Retry Policy
When a webhook delivery fails, we automatically retry with exponential backoff to maximize delivery reliability.
What counts as a failure?
A delivery is considered failed when your endpoint responds with any status code other than 200, 201, 202 or 204, or when the connection times out (30s timeout per attempt, configurable per webhook).
Retry schedule
Failed deliveries are retried up to 5 times in total by default (configurable per webhook), using exponential backoff: the delay roughly doubles before each new attempt, starting at ~2s. With default settings, all 5 attempts happen within about 30 seconds of the first one — a small random jitter is added to each delay so retries don't all land at the same instant.
| Attempt | Delay before this attempt |
|---|---|
| #1 | 0s |
| #2 | ~2s |
| #3 | ~4s |
| #4 | ~8s |
| #5 | ~16s |
Attempt #1 is the original delivery, sent immediately. Delays shown are approximate — actual values vary slightly due to jitter.
Permanent failure: Once the configured number of attempts is exhausted (5 by default), that delivery is permanently marked as failed. You can inspect failed deliveries in the webhook logs via the CRM settings.
Webhook auto-pause: This is separate from a single delivery's retries: if a webhook accumulates 10 consecutive failed delivery attempts (counted per attempt, including retries of the same event — not just distinct events), it is automatically paused and stops receiving any new events until it's manually reactivated in the CRM settings.
Idempotency
Because retries can deliver the same event more than once, your endpoint must be idempotent. Use the X-Webhook-Event-Id header to deduplicate: it stays the same across every delivery attempt for a given event, unlike X-Webhook-Delivery-Attempt, which increments on each retry.
const processedDeliveries = new Set(); // Use Redis in production
app.post('/webhook', (req, res) => {
const eventId = req.headers['x-webhook-event-id'];
if (processedDeliveries.has(eventId)) {
// Already processed — acknowledge and skip
return res.sendStatus(200);
}
// Process the event
handleEvent(req.body);
processedDeliveries.add(eventId);
res.sendStatus(200);
});Best practices
- Respond with 200 OK as fast as possible. Queue heavy processing asynchronously (SQS, BullMQ, etc.).
- Never return 5xx for business logic errors — respond 200 and log locally instead. Only 200, 201, 202 and 204 count as success; anything else (5xx especially) triggers a retry.
- Always validate the HMAC signature before processing (see Security page).
- Store the event ID (X-Webhook-Event-Id) in a database to safely handle duplicate events.
- Monitor your webhook endpoint's response times — keep them under 5s to avoid timeouts.