Retries & Replays
The webhook dispatcher retries on transient failures and gives up on permanent ones. This page covers the exact behaviour so your endpoint can plan for it.
Retry schedule
| Attempt | Backoff | When |
|---|---|---|
| 1 | — | Immediate (within seconds of the event) |
| 2 | 1 minute | After attempt 1 fails |
| 3 | 5 minutes | After attempt 2 fails |
| 4 | 15 minutes | After attempt 3 fails |
| Final | give up | After attempt 4 fails — no further retries |
Total retry window: ~21 minutes. After that the event is marked
gave_up in our audit log and no further attempts are made.
What triggers a retry
| Response | Retry? | Reason |
|---|---|---|
| 2xx (200–299) | No | Delivered |
| 3xx | Yes | We don’t follow redirects — set up your URL to respond directly |
| 4xx (except 408, 429) | No | Your endpoint is misconfigured / rejecting; retries waste both sides’ budget |
| 408 (timeout) | Yes | Treated as transient |
| 429 (rate limit) | Yes | Backoff respects Retry-After header up to 1 hour |
| 5xx | Yes | Transient — recipient claimed the event but couldn’t process |
| Network timeout | Yes | 10-second timeout per request |
Why 4xx doesn’t retry
Most 4xx responses indicate your endpoint is broken in a way time won’t fix:
- 401/403 — wrong shared secret
- 404 — endpoint doesn’t exist
- 405 — wrong HTTP method
- 422 — payload schema mismatch
If your endpoint needs time to recover (e.g. you just deployed +
the route 404s for 30 seconds), respond with 503 instead of 404
during the deploy window. We’ll retry.
Idempotency
Every webhook attempt carries a unique X-Splashify-Delivery-ID
header. Even retries of the same event have a fresh delivery ID.
To dedup at your end, key off mail.messageId + eventType:
const key = `${body.mail.messageId}:${body.eventType}`;
const inserted = await db.events.insertOne({
_id: key,
...body,
receivedAt: new Date(),
}, { ignoreDuplicates: true });
if (!inserted.insertedCount) {
return res.status(200).end(); // already processed, ack
}
// ... processThe combination is unique per event — even if we retry due to your
500 response, the second delivery has the same mail.messageId +
eventType and your insert dedups it.
Manual replay
For events that gave up (your endpoint was down for >21 minutes), contact support to replay them. We retain the full event log for 30 days and can re-fan-out a specific window.
Self-serve replay is on the roadmap — POST /partner/email/events/replay
with a time range + optional event-type filter.
Inspecting webhook delivery state
To see whether an event was delivered to a destination:
curl 'https://apis.splashifypro.com/api/v1/partner/email/events?day_bucket=2026-05-03' \
-H "Authorization: Bearer $SPLASHIFY_API_KEY"Response includes per-destination delivery status (delivered, failed, gave_up) so you can see which webhooks landed.
Common reasons for repeated failures
- Endpoint times out > 10 seconds. Your handler should ack ASAP (write to a queue, return 200) and process async.
- Endpoint behind Cloudflare with bot protection. Bot challenges
return 403 to our requests — whitelist our user-agent
(
Splashify-Pro-Webhook/1.0) or our outbound IP range. - HTTPS cert expired. We don’t disable cert verification. Renew + monitor.
- Wrong secret. If you rotated the secret in the panel but didn’t roll it on your endpoint, every signature check returns 401.