Webhooks Explained: Signed Payloads and Retries

Backend2026-10-27TryQuickToolBox

You've just integrated a payment provider's webhook to receive transaction updates. Everything works in testing, but in production you notice missing events during traffic spikes or duplicate charges when the provider retries. Webhooks are simple in concept but tricky in practice: they cross network boundaries, can be delayed, duplicated, or spoofed. This article explains how to build robust webhook consumers and producers using signed payloads and smart retry strategies.

What Are Webhooks?

Webhooks are user-defined HTTP callbacks triggered by events. When something happens in a source system (e.g., a new order), it sends an HTTP POST request to a URL you provide. Unlike polling, webhooks push data in near real-time, reducing latency and server load.

However, webhooks introduce challenges: ensuring the request actually came from the expected sender, handling failures gracefully, and avoiding duplicate processing.

Securing Webhooks with Signed Payloads

Without verification, anyone can send fake requests to your webhook endpoint. Signed payloads solve this by including a cryptographic signature that you can validate.

How HMAC Signatures Work

The sender computes a hash-based message authentication code (HMAC) using a shared secret and the request body. The signature is sent in a header (e.g., X-Signature). You recompute the HMAC on your side and compare.

// Node.js example: verifying a webhook signature
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Always use a constant-time comparison function like timingSafeEqual to prevent timing attacks.

Include a Timestamp

To prevent replay attacks, include a timestamp in the signed payload and reject requests older than a few minutes (e.g., 5 minutes). The timestamp should be part of the signed data.

Implementing Reliable Retries

Network issues or temporary server errors can cause webhook delivery to fail. A robust sender retries failed deliveries with exponential backoff.

Retry Strategy

Example: Retry with Backoff in Python

import time
import requests
from requests.exceptions import RequestException

def send_webhook(url, payload, max_retries=5):
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=payload, timeout=5)
            if response.status_code < 500:
                return True  # success or client error (no retry)
        except RequestException:
            pass
        time.sleep(2 ** attempt)  # exponential backoff
    return False

Best Practices for Webhook Consumers

Comparison: Webhooks vs Polling

AspectWebhooksPolling
LatencyNear real-timeDepends on interval
Server loadLow (push only)High (frequent requests)
ComplexityHigher (need endpoint, security)Lower
ReliabilityRequires retriesInherently retried

FAQ

What is a signed webhook payload?

A signed payload includes a cryptographic signature (usually HMAC) that allows the receiver to verify the request's authenticity and integrity.

How many times should I retry a failed webhook?

A common practice is 3–5 retries with exponential backoff, but it depends on your SLA and the criticality of the event.

Can I use webhooks without HTTPS?

No, always use HTTPS to protect the payload and signature from interception. Plain HTTP exposes sensitive data.

When debugging webhook payloads, you might need to format JSON quickly. Try our JSON Formatter to pretty-print and validate JSON responses.