Webhook Guides
Webhook Timeout Handling
Webhook timeouts are one of the easiest ways to create duplicate events. A provider sends a request, your receiver starts work, the response does not arrive quickly enough, and the provider retries. If the first attempt already changed state, the retry can repeat the same side effect.
The safer pattern is fast provider acknowledgement after durable capture, then controlled downstream delivery with retries, attempt history, and idempotency.
Timeout failure modes
- The provider times out and retries while the first attempt is still running.
- The receiver returns 200 after processing, but storage failed before the response.
- The receiver calls a slow third-party API inside the provider request path.
- The destination is overloaded and every provider retry adds more pressure.
- A replay is triggered before the receiver is idempotent.
Safer receiver shape
async function webhookEndpoint(req, res) {
const rawBody = await readRawBody(req);
await verifySignature(req.headers, rawBody);
const stored = await durableStore.insert({
headers: req.headers,
body: rawBody,
receivedAt: new Date()
});
res.status(202).json({ accepted: true, requestId: stored.id });
queue.enqueue({ requestId: stored.id });
}What to move out of the timeout path
| Work | Provider request path? | Better place |
|---|---|---|
| Signature verification | Yes | Before durable capture. |
| Durable request storage | Yes | Before response. |
| Billing, provisioning, fulfillment | No | Destination worker with idempotency. |
| Third-party API calls | No | Retried async destination branch. |
FastHook timeout pattern
Put FastHook at the provider edge when direct endpoints are too slow or too fragile. FastHook accepts and stores the provider request, then routes destination events through retryable attempts. You can inspect the timeout response, fix the receiver, retry one event, then replay a narrow window.