Skip to Content
WebhooksIntroduction

Webhooks

Webhooks let your app react to email lifecycle events as they happen. Send → Delivery → Open → Click → Bounce → Complaint → Reply — every state transition fires an HTTP POST to your URL with a structured JSON payload.

Two webhook surfaces

Pick whichever fits your integration:

SurfaceWhere you set itScopeBest for
Account-level webhookSettings → Webhook URL on the partner panelEvery event for every send + lifecycle events (production-access approval, account deletion, etc)Single endpoint that handles everything; simplest setup
Per-config-set destinationsPOST /partner/email/configuration-sets/:id/event-destinationsOnly events scoped to that config set, filtered by matching_event_typesPer-customer or per-product routing; advanced filtering

You can use both at once. The account-level URL receives every event regardless of config set; the per-config-set destinations receive their filtered subset additionally. Most partners start with just the account-level URL and add per-config-set destinations later when they need to route per-customer.

Event types fired to your webhook

EventWhenSurface
email.sentRecipient MX accepted the message (250 OK)both
email.deliveredSame as sent — direct-MX collapses theseboth
email.bouncedHard or soft bounce reported via DSNboth
email.complainedRecipient marked as spam (FBL)both
email.openedRecipient opened the email (1×1 pixel loaded)both
email.clickedRecipient clicked a link in the emailboth
email.rejectedSend refused at the API layer (suppression / quota)both
email.repliedRecipient replied to the emailboth
production_access.approvedAdmin lifted your sandbox capaccount-level only
production_access.deniedAdmin denied your sandbox lift requestaccount-level only
account.deletion.requestedYou requested account deletionaccount-level only
account.deletion.cancelledYou cancelled the deletion within the 48h windowaccount-level only
rcs_kyc.approvedAdmin approved a customer’s RCS KYCaccount-level only
rcs_kyc.rejectedAdmin rejected a customer’s RCS KYC (see data.rejection_reason)account-level only
rcs.message.textEnd-user replied with text — see RCS Incomingaccount-level only
rcs.message.mediaEnd-user sent media (image / video / PDF)account-level only
rcs.message.locationEnd-user shared a locationaccount-level only
rcs.suggestion.responseEnd-user tapped a reply / URL / dial / calendar suggestionaccount-level only
rcs.status.sentMessage you sent via send-rcs was accepted by the carrieraccount-level only
rcs.status.deliveredDelivered to the recipient’s deviceaccount-level only
rcs.status.readRecipient opened the messageaccount-level only
rcs.status.failedCarrier-side failure — see data.rm_event.failure_reasonaccount-level only
WABA_ONBOARDEDCustomer completed WhatsApp embedded signup — see WhatsApp Eventsaccount-level only
WhatsApp sent / delivered / read / failed / deletedOutbound message status — raw Meta envelope, forwarded as-isaccount-level only
WhatsApp inbound (text / image / video / audio / document / sticker / location / contacts / interactive / button / order / reaction / system / unknown)Customer-initiated messages — raw Meta envelope, forwarded as-isaccount-level only

How it works

  1. You create a configuration set (a logical grouping for sends).
  2. You add a webhook event destination to that config set, with the URL + secret + the event types you want.
  3. You send emails with configuration_set_name referencing that set.
  4. Every event for those sends gets POSTed to your URL with an HMAC-signed body.
  5. Retries: 5xx + timeout → 1min, 5min, 15min. 4xx → no retry (your URL is broken; we don’t waste retries).

Set up a webhook in 3 calls

1. Create a config set

curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production", "suppression_options": "BOUNCE_AND_COMPLAINT" }'

Save the config_set_id from the response.

2. Add a webhook destination

curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets/$CONFIG_SET_ID/event-destinations \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production-webhook", "destination_type": "WEBHOOK", "webhook_url": "https://yourapp.com/webhooks/splashify", "webhook_secret": "your-shared-secret-32-chars", "matching_event_types": ["send", "delivered", "bounce", "complaint", "open", "click"] }'

Security: Store the webhook_secret somewhere your endpoint can read it (env var). The API will never echo it back — to rotate, PATCH a new value.

3. Reference the config set on send

curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "[email protected]", "to": ["[email protected]"], "subject": "Welcome", "html_body": "<h1>Hi</h1>", "configuration_set_name": "production" }'

Within seconds your endpoint receives a Send event, then Delivery, etc.

Payload shape

We follow the AWS SES event-publishing JSON envelope. Code written against AWS SES SNS subscriptions consumes our webhooks by swapping the auth header verification.

{ "eventType": "Delivery", "mail": { "timestamp": "2026-05-03T12:34:56Z", "messageId": "550e8400-e29b-41d4-a716-446655440000", "source": "[email protected]", "destination": ["[email protected]"] }, "delivery": { "timestamp": "2026-05-03T12:34:57Z", "recipients": ["[email protected]"], "smtpResponse": "250 OK" } }

Every event has the top-level eventType and mail fields. The event-specific payload is nested under a key matching the lowercase event type — delivery, bounce, complaint, etc.

Headers

Every POST carries:

HeaderPurpose
Content-Typeapplication/json
User-AgentSplashify-Pro-Webhook/1.0
X-Splashify-EventEvent type — Send, Delivery, Bounce, …
X-Splashify-Signaturesha256=<hex> HMAC-SHA256 of the raw body keyed by your webhook_secret
X-Splashify-TimestampUnix seconds — protects against replay
X-Splashify-Delivery-IDUUID per delivery attempt — use for idempotency

Quick verify in Node

import crypto from "crypto"; app.post("/webhooks/splashify", express.raw({ type: "application/json" }), (req, res) => { const signature = req.header("x-splashify-signature") || ""; const expected = "sha256=" + crypto .createHmac("sha256", process.env.SPLASHIFY_WEBHOOK_SECRET) .update(req.body) .digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { return res.status(401).end(); } const event = JSON.parse(req.body.toString()); // process event... res.status(200).end(); });

Next