Webhook failures are rarely caused by one problem. A missing event may be filtered before delivery, rejected by the receiver, delayed in a retry queue, or accepted twice because the application did not handle redelivery safely. This guide provides a provider-agnostic workflow for webhook debugging, with delivery-log fields, signature-verification examples, retry and backoff options, an idempotency template, local testing steps, a replay procedure, and a practical failure-handling runbook.
Overview
A webhook is an HTTP request sent by one system when an event occurs in another system. The receiving application must treat that request as an untrusted, repeatable, and potentially delayed message. Reliability therefore depends on more than returning a successful HTTP status. It requires a complete path from event creation to delivery, authentication, validation, durable processing, acknowledgement, retry, and observation.
When debugging a webhook, establish where the event stopped. A useful provider-agnostic flow is:
- Was the event created? Confirm that the source action met the conditions for the event and that the source system recorded an event identifier.
- Was the event selected for delivery? Check event filters, subscription status, environment, tenant, and endpoint configuration.
- Was a delivery attempt made? Look for an attempt timestamp, destination, response status, timeout, and provider request identifier.
- Did the request reach your edge? Search the load balancer, API gateway, WAF, or ingress logs using the provider request identifier or your correlation data.
- Did authentication and validation pass? Confirm the signature, timestamp tolerance, content type, schema, and required headers.
- Was the event acknowledged? Separate transport acknowledgement from business processing. A receiver can return a success status before downstream work finishes, provided the payload is durably queued.
- Was the event processed exactly once from the business perspective? Use an idempotency record keyed by a stable event or delivery identifier.
This sequence prevents a common mistake: changing application code before proving whether the request reached the application at all. For status-code context, use the HTTP status codes API debugging reference.
What to track
Delivery-log fields
Keep provider delivery logs and receiver logs linkable. At minimum, capture these fields without storing sensitive payloads unnecessarily:
- Event identifier and delivery or attempt identifier
- Event type, schema version, source environment, and tenant where applicable
- Created-at timestamp, sent-at timestamp, received-at timestamp, acknowledged-at timestamp, and completed-at timestamp
- Destination endpoint or a safe endpoint identifier
- Attempt number, retry state, next scheduled attempt, and final disposition
- HTTP method, response status, response latency, timeout flag, and response-size limit result
- Provider request identifier, trace identifier, and internal correlation identifier
- Signature-verification result and validation failure category
- Payload size, content type, and schema-validation result
- Processing outcome, idempotency result, downstream dependency, and error class
Redact authorization headers, signatures, personal data, payment details, and other secrets. Store a payload hash or a protected reference when teams need to compare deliveries without copying the full body into ordinary logs. A formatted payload can help during investigation; a JSON formatter and validator workflow is useful for inspection, but do not paste production secrets into an unapproved tool.
Signature verification
Verify signatures against the exact raw request body, not a re-serialized JSON object. Whitespace, property order, Unicode encoding, and newline changes can alter the signed bytes. Keep the signing secret in an approved secret manager rather than source code or ordinary environment-variable files; see the configuration guide for the operational distinction.
A generic HMAC pattern in Node.js looks like this:
const crypto = require('crypto');
function verifySignature(rawBody, receivedSignature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
const actual = Buffer.from(receivedSignature, 'utf8');
const wanted = Buffer.from(expected, 'utf8');
return actual.length === wanted.length &&
crypto.timingSafeEqual(actual, wanted);
}The header format, digest encoding, timestamp rules, and signed fields vary by provider. Treat this as a pattern, not a drop-in integration. A Python equivalent is:
import hmac
import hashlib
def verify_signature(raw_body: bytes, received: str, secret: bytes) -> bool:
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received)Reject malformed signatures consistently, limit timestamp age when the protocol supports signed timestamps, and support controlled secret rotation where two verification secrets may be valid during a transition. Never log the secret or a complete authorization header. For broader authentication choices, compare API keys, OAuth 2.0, and mTLS in this cloud API authentication guide.
Retry and backoff choices
Retry policy should distinguish temporary delivery failures from permanent configuration or validation failures. The provider controls its own delivery schedule, while the receiver controls internal retries after accepting an event.
| Strategy | Best fit | Trade-off |
|---|---|---|
| Fixed delay | Simple integrations with predictable, low-volume failures | Can create synchronized retry spikes |
| Exponential backoff | Transient outages and overloaded dependencies | Recovery can take longer without a sensible maximum |
| Exponential backoff with jitter | Shared cloud services and many concurrent deliveries | Harder to predict exactly, but reduces synchronized load |
| Bounded retries plus dead-letter queue | Events that require investigation or manual replay | Needs queue monitoring and an explicit replay process |
| Immediate retry only for selected errors | Receivers that can classify rate limits, timeouts, and validation failures | Incorrect classification can discard recoverable events |
Do not assume a universal retry count or retention period. Provider retry limits and schedules differ and may change. Record the active policy for each integration, including which status codes, connection errors, and timeouts cause another attempt. A receiver should generally return a failure when it has not durably accepted the event; otherwise the sender may consider the event delivered even though processing was lost.
Idempotency template
At-least-once delivery means the same event can arrive more than once. Make the business operation idempotent by storing a stable event identifier before performing a non-repeatable action, or by using a database constraint that makes the operation safe to repeat.
BEGIN;
-- Unique constraint: (consumer_name, event_id)
INSERT INTO webhook_receipts
(consumer_name, event_id, received_at, status)
VALUES
(:consumer, :event_id, CURRENT_TIMESTAMP, 'received')
ON CONFLICT (consumer_name, event_id) DO NOTHING;
-- If no row was inserted, the event is already being handled or completed.
-- Otherwise, enqueue durable work using the same transaction where possible.
COMMIT;Use the event identifier supplied by the source system when it is stable across delivery attempts. If it is not stable, use the provider’s documented deduplication key or define a carefully reviewed business key. Record whether a duplicate was ignored, still processing, completed, or failed. An idempotency key alone does not make an external side effect safe: downstream calls may need their own idempotency key or a reconciliation step.
Cadence and checkpoints
Webhook reliability should be reviewed on a recurring schedule rather than only after an incident. A monthly check is appropriate for active integrations; a quarterly review may suit low-volume, stable endpoints. Review immediately after a provider change, endpoint migration, authentication change, schema update, or production deployment.
- Monthly: inspect delivery success, timeout, rejection, duplicate, and dead-letter trends by endpoint and event type.
- Monthly: sample successful and failed deliveries to confirm that correlation identifiers, signatures, and response classifications are still recorded.
- Quarterly: test replay in a non-production environment, verify that idempotency records prevent duplicate side effects, and confirm that operators know the runbook.
- Quarterly: review secret rotation, access permissions, payload retention, alert thresholds, and the documented provider retry behavior.
- After changes: run a controlled test for valid delivery, invalid signature, malformed payload, timeout, duplicate delivery, and downstream failure.
Keep a small integration inventory with the endpoint owner, source system, event types, schema version, authentication method, retry policy, alert destination, and last verification date. This turns webhook debugging from individual memory into a repeatable team workflow.
How to interpret changes
Look for relationships, not just a single failure count. A rise in timeouts with normal application error rates can indicate a slow dependency, network path issue, or overly strict sender timeout. More signature failures after a deployment suggest a raw-body handling change, encoding mismatch, clock issue, or secret-rotation problem. More duplicates alongside increased latency may indicate that the receiver is acknowledging after business work instead of after durable acceptance.
Classify failures into four operational groups:
- Source or subscription: the event was never selected, the wrong environment was configured, or filtering excluded it.
- Transport: DNS, TLS, routing, firewall, gateway, timeout, or rate-limit issues prevented a useful response.
- Validation and authentication: signature, timestamp, content type, schema, or authorization checks failed.
- Processing: the request was accepted but a database, queue, or downstream service failed.
Track latency in stages: source-to-send, send-to-receive, receive-to-acknowledge, and acknowledge-to-complete. Averages can hide a queueing problem, so inspect high-percentile behavior and representative traces where your observability system supports it. Alert on sustained trends and unprocessed backlog, not merely on an isolated failed attempt.
Contract changes deserve special attention. Compare the received payload with the expected schema, tolerate additive fields where appropriate, and reject incompatible changes clearly. Contract tests can catch mismatches before deployment; see the OpenAPI contract testing workflow. If an endpoint is being migrated, a controlled rollout strategy such as blue-green, canary, or rolling deployment can reduce the blast radius; the trade-offs are covered in this cloud release strategy guide.
When to revisit
Revisit this guide whenever an event goes missing, arrives late, is rejected, or creates a duplicate business action. Also schedule a formal review every month or quarter according to integration criticality. Refresh the protocol notes, provider retry limits, signing-library examples, and observability fields at least every six months, or sooner after a major provider change. Do not copy a previous integration’s assumptions into a new one without checking its event identifiers, signature format, retry behavior, and delivery-log capabilities.
Failure-handling runbook
- Open the event and delivery record using the source event identifier, provider request identifier, or internal trace identifier.
- Confirm the event type, subscription, environment, destination, and delivery attempt count.
- Check edge, application, queue, and downstream logs in that order, preserving timestamps and correlation identifiers.
- Classify the failure as source, transport, validation, or processing.
- Contain the issue: disable a faulty subscription, pause unsafe downstream work, or route events to a protected queue when appropriate.
- Fix the cause and test with a safe event. Confirm signature verification, schema validation, acknowledgement timing, and idempotent processing.
- Replay only the affected events, using the provider’s replay mechanism or a controlled internal tool. Preserve original event identifiers and mark replay attempts clearly.
- Verify that successful replay did not create duplicate side effects, then document the cause, duration, affected event range, and preventive change.
Before calling an incident resolved, confirm four outcomes: the source has stopped accumulating failed deliveries, the receiver is acknowledging only durable work, the processing backlog is draining, and duplicate protection remains active. Keep the runbook beside the integration inventory so the next webhook debugging session starts with evidence rather than guesswork. For a wider readiness review, use the production readiness checklist for cloud-native applications.