Webhooks Explained: Signed Payloads and Retries

Backend2026-10-26TryQuickToolBox

Why Webhooks Need Signed Payloads and Retries

You've just integrated a third-party service that sends webhooks to your endpoint. Everything works in testing, but in production you notice missing events, duplicate deliveries, and worse—someone spoofing requests to your webhook URL. Without proper security and reliability measures, webhooks can become a source of bugs and vulnerabilities.

Webhooks are HTTP callbacks that notify your application when an event occurs. They're simple in concept but tricky in practice because they cross trust boundaries and network reliability issues. In this guide, we'll cover two critical aspects: signed payloads to verify authenticity and retries to ensure delivery. We'll walk through practical implementations you can apply immediately.

Understanding Signed Payloads

A signed payload is a way for the sender to prove the webhook came from them and wasn't tampered with. The sender computes a cryptographic signature over the request body (and sometimes headers) using a shared secret, then includes that signature in a header. Your endpoint recomputes the signature and compares it.

The most common algorithm is HMAC-SHA256. It's fast, secure, and widely supported. Here's how it works:

  1. The sender and receiver share a secret key (configured in the webhook settings).
  2. For each webhook, the sender creates a signature by hashing the payload with the secret using HMAC-SHA256.
  3. The signature is sent in a header like X-Signature.
  4. The receiver computes the same HMAC and compares it using a constant-time comparison to prevent timing attacks.

Implementing HMAC Verification in Node.js

Let's see a concrete example. Suppose the sender includes the signature in X-Signature as a hex string. Here's how you verify it in an Express route:

const crypto = require('crypto');
const express = require('express');
const app = express();

// Use raw body for signature verification
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const event = JSON.parse(req.body);
  console.log('Received event:', event.type);

  res.status(200).send('OK');
});

app.listen(3000);

Important: Always use the raw request body for signature computation, not the parsed JSON. Parsing and re-stringifying can change whitespace and ordering, breaking the signature.

Timestamp and Replay Protection

Signatures alone don't prevent replay attacks. An attacker could capture a valid signed request and resend it later. To mitigate this, include a timestamp in the signed payload or in a header. For example, add a X-Timestamp header with the Unix time. The receiver checks that the timestamp is within a tolerance window (e.g., 5 minutes) and rejects older requests.

Also, include the timestamp in the signature computation to prevent tampering. The sender computes HMAC over timestamp + '.' + body. The receiver does the same and compares.

Implementing Retries for Reliable Delivery

Webhooks travel over the public internet, so failures are inevitable: network timeouts, server errors (5xx), or your endpoint being temporarily down. A robust webhook system must retry failed deliveries.

Retry Strategies

Common retry strategies include:

A typical policy might be: retry up to 5 times with exponential backoff and jitter, starting at 10 seconds. After that, mark the delivery as failed and possibly alert.

Idempotency: Handling Duplicates

Retries can cause duplicate deliveries. Your endpoint must be idempotent—processing the same event twice should not cause side effects like double-charging a customer. Include a unique event ID in the payload (e.g., event.id) and store processed IDs for a reasonable period. Before processing, check if the ID has been seen.

Alternatively, design your operations to be naturally idempotent (e.g., using upserts instead of inserts).

Responding Quickly

Your webhook endpoint should acknowledge receipt quickly (within a few seconds) and process the event asynchronously. If processing takes too long, the sender may time out and retry, causing duplicates. Use a queue: respond with 200 OK immediately after verifying the signature, then enqueue the event for background processing.

Comparison of Webhook Security Measures

Measure Purpose Implementation Complexity
HMAC signature Verify authenticity and integrity Low
Timestamp validation Prevent replay attacks Low
Idempotency keys Avoid duplicate processing Medium
IP allowlisting Restrict to known sender IPs Low (but brittle)
HTTPS Encrypt data in transit Low

While IP allowlisting can add a layer, it's not reliable because sender IPs may change. Rely primarily on signatures.

Testing Your Webhook Endpoint

Use tools like ngrok to expose your local server and trigger test webhooks from the provider's dashboard. Verify that signatures are validated correctly and that retries work by simulating failures (e.g., return 500 for the first few attempts).

When debugging, you might need to inspect JSON payloads. The JSON Formatter can help you pretty-print and validate webhook payloads quickly.

FAQ

What is a signed webhook payload?

A signed payload includes a cryptographic signature (usually HMAC) that allows the receiver to verify the request came from the expected sender and wasn't altered. It's computed using a shared secret.

How many times should I retry a failed webhook?

There's no universal number, but 3–5 retries with exponential backoff are common. After that, log the failure and consider alerting. The goal is to handle transient issues without infinite retries.

Can I use JWT for webhook signatures?

Yes, some providers use JWT (JSON Web Tokens) to sign webhooks. The token contains claims and is signed with a secret or private key. Verification is similar: validate the signature and check claims like expiration.

Ready to implement secure webhooks? Start by generating a strong secret, adding HMAC verification, and setting up retries with idempotency. For quick payload inspection during development, try our JSON Formatter to format and validate webhook JSON.