Webhooks Explained: Signed Payloads and Retries
You've just integrated a payment gateway or a CI/CD service, and now you need to receive real-time events. Webhooks are the standard solution, but they come with pitfalls: unauthenticated payloads, lost events, and duplicate deliveries. This article explains how webhooks work, how to sign payloads to prevent tampering, and how to implement retries for reliable delivery.
What Are Webhooks?
A webhook is a user-defined HTTP callback. Instead of your application polling an API for updates, the provider sends an HTTP POST request to a URL you specify whenever an event occurs. This is more efficient and enables near real-time reactions.
Common use cases include:
- Payment notifications (e.g., Stripe, PayPal)
- CI/CD build status (e.g., GitHub, GitLab)
- Messaging platforms (e.g., Slack, Discord)
- CRM updates (e.g., Salesforce)
Why Sign Webhook Payloads?
Webhook endpoints are publicly accessible URLs. Without verification, anyone can send fake payloads, leading to data corruption or security breaches. Signing payloads with a shared secret ensures authenticity and integrity.
Most providers use HMAC (Hash-based Message Authentication Code) with SHA-256. The provider computes a signature over the raw request body using a secret key, and includes it in a header (e.g., X-Hub-Signature-256). Your server recomputes the signature and compares it.
How to Verify a Signature
Here's a step-by-step guide:
- Retrieve the raw body exactly as sent. Do not parse or modify it before verification.
- Extract the signature from the header.
- Compute the expected signature using HMAC-SHA256 with your secret.
- Compare using a constant-time function to prevent timing attacks.
Example in Node.js:
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = 'sha256=' + hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
}
In Python:
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(f'sha256={expected}', signature)
Always use a constant-time comparison to avoid leaking information.
Implementing Retries for Reliability
Networks fail, servers restart, and deployments happen. A robust webhook system must retry failed deliveries. Providers typically retry with exponential backoff, but you should also handle retries on your receiving end.
Retry Strategies
- Exponential backoff: Wait 1s, 2s, 4s, 8s, etc., between retries.
- Maximum attempts: Limit retries to avoid infinite loops (e.g., 5 attempts).
- Dead letter queue: After max attempts, store the event for manual inspection.
- Idempotency: Ensure processing the same event multiple times doesn't cause duplicate side effects.
Idempotency Keys
Many providers include a unique event ID (e.g., X-Event-ID). Store processed IDs in a database or cache to skip duplicates. For example, use Redis with a TTL to track seen IDs.
Comparison: Polling vs Webhooks
| Aspect | Polling | Webhooks |
|---|---|---|
| Latency | Depends on interval | Near real-time |
| Server load | High (constant requests) | Low (only on events) |
| Complexity | Simple to implement | Requires endpoint, security, retries |
| Reliability | Misses events between polls | Can miss if endpoint down; retries help |
Best Practices for Webhook Consumers
- Respond quickly: Return 2xx within a few seconds; process asynchronously.
- Validate signatures: Always verify before processing.
- Log everything: Keep raw payloads and headers for debugging.
- Use HTTPS: Never accept webhooks over plain HTTP.
- Monitor failures: Set up alerts for repeated retry failures.
FAQ
What is a webhook signature?
A webhook signature is an HMAC hash of the payload, created with a shared secret. It allows the receiver to verify that the request came from the expected sender and wasn't tampered with.
How many times should I retry a failed webhook?
There's no universal number, but 3–5 retries with exponential backoff is common. After that, log the event to a dead letter queue for manual review.
Can I use webhooks without HTTPS?
Technically yes, but it's highly insecure. Always use HTTPS to encrypt payloads and prevent man-in-the-middle attacks.
Ready to test your webhook endpoints? Use our JSON Formatter to inspect and validate payloads quickly.