Skip to Content
WebhooksRetries & Replays

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

AttemptBackoffWhen
1Immediate (within seconds of the event)
21 minuteAfter attempt 1 fails
35 minutesAfter attempt 2 fails
415 minutesAfter attempt 3 fails
Finalgive upAfter 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

ResponseRetry?Reason
2xx (200–299)NoDelivered
3xxYesWe don’t follow redirects — set up your URL to respond directly
4xx (except 408, 429)NoYour endpoint is misconfigured / rejecting; retries waste both sides’ budget
408 (timeout)YesTreated as transient
429 (rate limit)YesBackoff respects Retry-After header up to 1 hour
5xxYesTransient — recipient claimed the event but couldn’t process
Network timeoutYes10-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 } // ... process

The 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.