My webhook isn't arriving or signature verification fails
A checklist for debugging missing webhook deliveries and signature verification errors.
Most webhook problems fall into two buckets: the delivery never reaches your server, or it reaches you but fails signature verification. Work through the relevant checklist below.
Deliveries aren't arriving
-
Is the endpoint subscribed to that event group? In the dashboard, subscriptions are chosen by group, not per event. There are three: Payment events (
payment_order.*,payment.*), Merchant subscription events (end_user_subscription.*,end_user_invoice.*), and Operational events (address.pool.lowand theagent.action.*approvals). An endpoint subscribed only to payment events won't receive subscription or operational/agent events, and vice versa. Leaving all three unchecked isn't allowed; checking all three means "all events", including any added in the future. (The API'senabled_eventsfield can technically carry an arbitrary subset, but the dashboard's group model can't represent an arbitrary subset. Saving a custom subset there widens it to the whole group.) -
Is your server returning
2xxwithin 10 seconds? Any non-2xxstatus, a network error, or no response within 10 s counts as a failure. A slow handler that does real work before responding is a common cause. Return200first, then process asynchronously. -
Check the delivery log. Each attempt records
response_status,response_duration_ms,error_message,attempts,next_retry_at, andstatus(pending/succeeded/failed/dead_letter). This usually tells you immediately whether the problem is on your side. -
Account for retries. Failed deliveries retry on this curve, then move to the dead letter queue on attempt 6:
Attempt Delay 1 30 s 2 60 s 3 5 min 4 30 min 5 2 h 6+ DLQ -
Replay once it's fixed. The replay endpoints (and the dashboard's replay button) enqueue a brand-new delivery row; the original audit log is preserved. Use replay-dead-letters to drain anything that hit the DLQ while your endpoint was down.
Signature verification fails
-
Verify against the raw body. Compute the signature over the exact bytes you received, before any JSON parsing or re-serialization. Frameworks that parse and re-stringify the body will change it and break the signature. This is the single most common cause.
-
Read the signature from the right header. Use the
SIGNATURE_HEADERconstant from the SDK rather than hardcoding the name. -
Use the correct secret. The
secretis shown only on create and rotate. If you didn't store it, rotate to get a new one. -
During rotation, accept both secrets. After you rotate, the previous secret stays valid alongside the new one for 24 hours. Pass both to the verifier so in-flight deliveries don't fail mid-rollout:
import { SIGNATURE_HEADER, verifySignature } from '@stableops/api-sdk/webhooks' const result = verifySignature({ secrets: [currentSecret, previousSecret], // both valid during the 24h overlap header: request.headers.get(SIGNATURE_HEADER) ?? undefined, rawBody, }) if (!result.ok) { console.error('Invalid signature:', result.reason) // Reject the request }
Related
How is this guide?
Last updated