Skip to Content
WebhooksBest Practices

Webhook Best Practices

1. Verify the signature on every request

Constant-time HMAC compare. See Verify Signature for per-language code.

2. Ack fast, process async

Your endpoint has 10 seconds before we time out + retry. The retry DOES NOT mean the first call failed — we have no idea until we get a response. Best pattern:

app.post("/webhooks/splashify", express.raw({...}), async (req, res) => { // 1. Verify signature // 2. Push to internal queue await queue.push({ rawBody: req.body.toString(), receivedAt: Date.now() }); // 3. Ack immediately res.status(200).end(); });

Process the queue with retries inside your own infrastructure. Your endpoint becomes a fast Layer-7 doorman.

3. Dedup by mail.messageId + eventType

Retried deliveries have the same logical content but different X-Splashify-Delivery-ID headers. Use the body fields as your dedup key:

const key = `${body.mail.messageId}:${body.eventType}`;

4. Handle out-of-order delivery

The dispatcher fans out one POST per (event, destination) combo in parallel — events for the same email may arrive in any order. A Bounce event might land before the Send event for the same messageId if the bounce was inline.

Don’t assume Send → Delivery → Open → Click arrive in order. Drive your state machine off the absolute event types, not their sequence.

5. Monitor your endpoint’s health

Webhook delivery metrics are available via:

curl 'https://apis.splashifypro.com/api/v1/partner/email/events?day_bucket=$(date -u +%Y-%m-%d)' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY"

Track:

  • Delivery rate — % of events delivered to your endpoint
  • Average latencydelivered_at - created_at
  • Failed events — events with status=failed or gave_up

A sudden drop in delivery rate usually means your endpoint started returning 5xx — check your app logs.

6. Don’t expose your webhook URL publicly

The HMAC signature stops forged events, but exposing the URL still attracts unwanted traffic (probes, fuzzing). Guard at the network edge:

  • Only allow POST (not GET / OPTIONS)
  • Whitelist our outbound IP range if your firewall supports it. IPs are published at status.splashifypro.com/ip-ranges  and updated when new sending capacity is added — subscribe to the changelog so you don’t miss additions.
  • Reject any request without X-Splashify-Signature

7. Use one webhook per concern

Don’t have one giant webhook handler that branches on eventType. Have separate config-set destinations for distinct concerns:

config_set "production-engagement" → /webhooks/engagement → matching_event_types: ["open", "click"] config_set "production-deliverability" → /webhooks/deliverability → matching_event_types: ["bounce", "complaint", "reject"] config_set "production-archive" → /webhooks/archive → matching_event_types: ["send", "delivered", "bounce", "complaint", "open", "click", "reject"]

Smaller handlers = easier to reason about + scope failures.

8. Log raw payloads for the first 30 days

Keep raw event bodies + headers in your logs (with the webhook_secret redacted). When something looks weird, you have the original payload to diff against your handler’s behaviour.

9. Replay before going to production

Use a tool like Webhook.site  to point test sends at — verify your signature check, JSON parsing, and event-type dispatch logic before pointing your real endpoint at us.

10. Have a fallback

Webhooks can drop. If your business depends on knowing whether an email delivered, periodically poll /emails/:message_id for any message you sent in the last hour that hasn’t reached a terminal state via webhook. Reconcile the difference.