Why Webhook Debugging Is Hard
Webhooks are the glue of modern software. Stripe sends payment events, GitHub sends push events, Shopify sends order events. But when a webhook doesn't work, debugging it is uniquely painful:
- You can't see the request. It's sent from a third party to your server. Unlike API calls you initiate, you have no control over timing or content.
- Retries mask the problem. Most webhook providers retry failed deliveries. By the time you check, the original error might be buried under successful retries.
- Local development is blind. Your laptop doesn't have a public URL. You can't receive webhooks without a tunnel.
- Payloads vary. The same event type can have different shapes depending on your account configuration, API version, or edge cases the docs don't mention.
The Debugging Workflow
Step 1: Capture the Raw Request
Before you fix anything, you need to see what's actually being sent. Create a temporary endpoint that logs everything:
# Using WebhookVault - instant webhook endpoint, no signup
curl -X POST https://webhookvault.anethoth.com/api/v1/demo/endpoint
# Returns:
# {
# "endpoint_url": "https://webhookvault.anethoth.com/hook/demo_abc123",
# "inspect_url": "https://webhookvault.anethoth.com/demo/inspect/abc123",
# "expires_in": "1 hour"
# }
Point your webhook provider at this URL. Now you can inspect every header, every payload byte, and every response your endpoint would return.
Step 2: Verify the Payload Structure
The most common webhook bug: your code expects a different payload shape than what arrives. Check for:
- Nested objects where you expect flat fields
- Arrays where you expect single objects
- String amounts where you expect numbers (Stripe sends amounts as integers in cents)
- Missing fields in certain event types (not all Stripe events include
customer) - Different field names between test mode and live mode
Step 3: Check Signature Verification
Most webhook providers sign their payloads so you can verify authenticity. This is the second most common failure point after payload parsing.
Common signature verification mistakes:
- Reading the body twice. Many frameworks consume the request body stream on first read. If your middleware reads it before your signature check, the check sees an empty body.
- Wrong encoding. Signature verification requires the raw body bytes, not a parsed JSON object.
- Stale secrets. You rotated the webhook secret in the provider's dashboard but forgot to update your
.envfile. - Clock skew. Some providers reject signatures if your server's clock is too far off. Use NTP.
Step 4: Replay Failed Deliveries
Once you've fixed the bug, you need to reprocess the events that failed. Options:
- Provider replay: Stripe, GitHub, and Shopify all let you re-send events from their dashboards.
- Endpoint replay: If you captured the original request (via WebhookVault or your own logging), replay it to your fixed endpoint.
- Idempotency keys: If your handler uses idempotency keys (it should!), replaying is safe — duplicate events are ignored.
Provider-Specific Tips
Stripe Webhooks
- Always verify the
stripe-signatureheader. Use the official SDK:stripe.Webhook.construct_event(payload, sig, secret) - Handle both
checkout.session.completedANDinvoice.paidfor subscription flows - Amounts are in cents (integer).
2000= $20.00 - Test with Stripe CLI:
stripe listen --forward-to localhost:8080/webhook
GitHub Webhooks
- Signature is HMAC-SHA256 in the
X-Hub-Signature-256header - Payload size can be large for push events with many commits — handle gracefully
- The
actionfield differentiates sub-events (e.g., PR opened vs. merged vs. closed)
Shopify Webhooks
- HMAC verification uses
X-Shopify-Hmac-Sha256header with Base64 encoding - Webhooks are tied to app installations — they stop when the merchant uninstalls
- Mandatory webhooks (GDPR) must be implemented or your app gets rejected
Best Practices for Production Webhooks
- Return 200 immediately, then process asynchronously. Webhook providers have short timeouts (5-30 seconds). If your processing takes longer, the provider retries, causing duplicate events.
- Implement idempotency. Store the event ID and skip duplicates. Webhooks are delivered "at least once," not "exactly once."
- Log everything. Log the full request (minus sensitive data) before processing. When something breaks, you need the original payload.
- Set up dead letter queues. Events that fail processing after N retries should go somewhere reviewable, not disappear.
- Monitor delivery health. Track your webhook success rate. A sudden drop from 99% to 90% means something broke.
Try WebhookVault free
Everything you need to know about debugging webhook integrations — from capturing payloads to replaying failed deliveries. Get started with our free tier — no credit card required.
Get started free →