Webhooks Explained: Signed Payloads and Retry Strategies

Backend2026-10-21TryQuickToolBox

You've built an API that sends webhooks to your users' endpoints. But then you face the classic problems: how do you ensure the payload hasn't been tampered with? What happens when the endpoint is down? How do you prevent duplicate processing? In this article, we'll tackle these challenges head-on with signed payloads and robust retry strategies.

Why Webhooks Need Signatures

Webhooks are HTTP callbacks that notify other systems about events. Because they're sent over the public internet, they're vulnerable to interception and spoofing. Without a signature, a malicious actor could send fake webhooks to your users, triggering unintended actions. Signing payloads with a shared secret ensures authenticity and integrity.

How Signed Payloads Work

The most common approach is HMAC (Hash-based Message Authentication Code) with SHA-256. The sender and receiver share a secret key. The sender computes a signature over the payload (and sometimes a timestamp) and includes it in a header. The receiver recomputes the signature and compares it to the received one.

Step-by-Step: Signing a Payload

  1. Create the payload: Serialize your event data to JSON. Ensure consistent formatting (e.g., no extra whitespace) because the signature is computed over the exact bytes.
  2. Add a timestamp: Include a Unix timestamp in the payload or header to prevent replay attacks. The receiver can reject requests older than a few minutes.
  3. Compute the HMAC: Use your secret key to generate the HMAC-SHA256 of the payload string (or timestamp + payload).
  4. Attach the signature: Send it in a header like X-Signature.

Here's an example in Python:

import hmac
import hashlib
import json
import time

secret = b'your_shared_secret'
payload = {"event": "user.created", "data": {"id": 123}}
body = json.dumps(payload, separators=(',', ':'))
timestamp = str(int(time.time()))
signed_content = timestamp + '.' + body
signature = hmac.new(secret, signed_content.encode(), hashlib.sha256).hexdigest()

# Send headers: X-Signature: signature, X-Timestamp: timestamp

Verifying the Signature

On the receiving end, you must:

Example verification in Node.js:

const crypto = require('crypto');

function verifySignature(req, secret) {
  const signature = req.headers['x-signature'];
  const timestamp = req.headers['x-timestamp'];
  if (!signature || !timestamp) return false;
  if (Date.now() / 1000 - timestamp > 300) return false; // 5 min
  const body = JSON.stringify(req.body);
  const signed = timestamp + '.' + body;
  const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

Designing Retry Strategies

Even with signatures, webhooks can fail due to network issues, server errors, or timeouts. A robust retry strategy ensures delivery without overwhelming the receiver.

Key Considerations

Implementing Retries

Most webhook senders use a queue system (e.g., Redis, RabbitMQ, or a database) to schedule retries. Here's a simplified flow:

  1. On event trigger, enqueue a webhook job with the payload and target URL.
  2. Worker picks up the job and attempts delivery.
  3. If the response status is 2xx, mark as success.
  4. If not, schedule a retry with exponential backoff, up to max attempts.
  5. After max attempts, mark as failed and notify.

Example retry schedule:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours

Best Practices for Webhook Security

FAQ

What is a signed webhook payload?

A signed payload includes a cryptographic signature (usually HMAC) that allows the receiver to verify the sender's authenticity and that the payload hasn't been tampered with.

How many times should I retry a failed webhook?

There's no one-size-fits-all answer, but a common approach is 3–5 retries with exponential backoff. Consider the criticality of the event and the receiver's capacity.

Can I use JWT for webhook signatures?

Yes, JWT can be used, but it's more complex. HMAC is simpler and sufficient for most use cases. JWT is useful if you need to include claims or expiration within the signature itself.

Ready to test your webhook payloads? Use our JSON Formatter to validate and pretty-print your JSON payloads before sending.