Webhooks Explained: Signed Payloads and Retries
Why Webhooks Are Both Powerful and Fragile
Webhooks power real-time integrations: payment notifications, CI/CD triggers, chat messages. But they come with two big challenges: security (how do you know the request is from the expected sender?) and reliability (what if the receiver is down?). This article shows you how to address both with signed payloads and retry strategies.
What Is a Webhook?
A webhook is an HTTP POST request sent by a provider to a consumer when an event occurs. Unlike polling, webhooks push data in near real-time, reducing latency and server load. The provider must ensure the request is authentic; the consumer must process it reliably.
Securing Webhooks with Signed Payloads
A signed payload uses a secret key to create a cryptographic signature (usually HMAC-SHA256) of the request body. The receiver recomputes the signature and compares it to the one in the header. If they match, the payload is authentic and untampered.
How to Generate and Verify Signatures
Here's a typical flow:
- Provider and consumer share a secret key (e.g., via dashboard).
- Provider computes
HMAC-SHA256(secret, payload)and sends it in a header likeX-Signature. - Consumer reads the raw body, computes the same HMAC, and compares using a constant-time function.
Example in Node.js:
const crypto = require('crypto');
function verifySignature(secret, payload, signature) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const digest = hmac.digest('hex');
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
}
Always use constant-time comparison to prevent timing attacks. Never parse the body before verification—use raw body middleware.
Implementing Retries for Reliable Delivery
Even with signatures, network failures or temporary outages can cause webhook delivery to fail. A robust retry mechanism ensures eventual delivery.
Retry Strategies
- Exponential backoff: Wait longer between each retry (e.g., 1s, 2s, 4s, 8s).
- Maximum attempts: Limit retries (e.g., 5 attempts) to avoid infinite loops.
- Dead letter queue: After max attempts, store the event for manual inspection.
- Idempotency: Include a unique event ID so the consumer can ignore duplicates.
Example retry logic in Python:
import time
import requests
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 == 200:
return True
except requests.RequestException:
pass
time.sleep(2 ** attempt) # exponential backoff
return False
Best Practices for Webhook Consumers
- Respond with 2xx quickly; process asynchronously if needed.
- Validate the signature before any processing.
- Use idempotency keys to handle duplicate deliveries.
- Log all incoming webhooks for debugging.
- Monitor for failures and alert on repeated errors.
Comparison: Polling vs Webhooks
| Aspect | Polling | Webhooks |
|---|---|---|
| Latency | High (interval-based) | Low (real-time) |
| Server load | Constant requests | Only on events |
| Complexity | Simple | Requires retry/security |
| Use case | Small scale, infrequent updates | Real-time integrations |
FAQ
What is a signed webhook payload?
A signed payload includes a cryptographic signature (e.g., HMAC) that allows the receiver to verify the request came from a trusted source and wasn't tampered with.
How many times should I retry a failed webhook?
There's no universal number, but 3–5 attempts with exponential backoff is common. After that, log the event for manual review.
Can I use JWT instead of HMAC for webhook signatures?
Yes, some providers use JWT. The principle is the same: verify the token's signature and claims before trusting the payload.
Need to inspect webhook payloads or logs? Try our JSON Formatter to pretty-print and validate JSON payloads quickly.