Core APIs
Webhooks
How per-message delivery webhooks actually work: the webhook_url field, real headers, and the real retry schedule.
WeZend's delivery webhooks are per-message: you pass webhook_url directly on the send request (or per-message inside a bulk request), and status updates for that message are POSTed there as they happen. For account-wide platform events there is also /v1/webhook-endpoints — see below.
{
"to": "+4512345678",
"channel": "sms",
"message": "Your order has shipped.",
"webhook_url": "https://yourapp.com/hooks/wezend"
}
Payload and headers
Every delivery includes:
X-WeZend-Event— the event nameX-WeZend-Timestamp— Unix ms timestamp used in the signatureX-WeZend-Signature— HMAC-SHA256 of${timestamp}.${JSON.stringify(payload)}, using the webhook signing secret from Settings → Webhook secret
Every delivery also carries legacy
X-ZafeConnect-*aliases of the same three headers, kept from before the rebrand so existing integrations keep verifying. The values are identical to theX-WeZend-*headers — verify against whichever prefix you already use.
Verify the signature
const crypto = require("crypto");
function verify(rawBody, timestamp, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
Retries
If your endpoint doesn't respond 2xx, delivery is retried 3 times — after 30 seconds, 5 minutes, then 30 minutes — and retries survive a restart or deploy (they're durable, backed by the database, not an in-memory queue). Respond quickly and process asynchronously rather than doing slow work in the request handler.
Platform-event webhooks
Besides per-message delivery webhooks, you can register account-wide outbound endpoints: POST /v1/webhook-endpoints with { url, events } creates one (manage with GET/PATCH/DELETE, and POST /v1/webhook-endpoints/:id/test delivers a ping). Either credential works — an API key or a dashboard JWT from POST /v1/auth/login — so a server-side integration can subscribe itself without anyone opening the dashboard. Each endpoint gets its own whsec_... signing secret, shown once at creation.
curl -X POST https://api.wezend.com/v1/webhook-endpoints \
-H "X-API-Key: $WEZEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/hooks/wezend", "events": ["contact.unsubscribed"] }'
If the key was issued with an explicit scope list, it needs write to create, update or delete an endpoint. A read-scoped key can list endpoints but gets 403 insufficient_scope on the mutations, with the missing scope named in the response body. Keys issued without a scope list keep full access.
Nine event types can be subscribed to in events. An endpoint registered with an empty events array receives all of them.
| Event | Fires when |
|---|---|
contact.created | A contact is created |
contact.updated | A contact's fields or traits change |
contact.unsubscribed | New — a contact opts out. Emitted from both suppression writers: the unsubscribe path and the SMS STOP-keyword path |
contact.bounced | New — email feedback reports a bounce for the contact |
message.delivered | A message is confirmed delivered |
message.failed | A message fails permanently |
message.received | New — an inbound email reply arrives |
campaign.sent | A campaign finishes sending |
form.submitted | A hosted form is submitted. The event name was already in the list, but nothing emitted it before this release — it now fires on every submission |
Subscribe to contact.unsubscribed to keep your own consent records in sync. Before this event existed, nothing pushed an opt-out back out of WeZend: if your system of record is something else — a CRM, a dealer portal, your own database — there was no way to learn that a contact had replied STOP, and you would keep treating them as subscribed. contact.bounced is a deliverability signal rather than an explicit opt-out, so keep the two apart in your own data.
Inbound vendor delivery receipts
The /v1/webhooks/messente, /v1/webhooks/twilio, /v1/webhooks/vonage and /v1/webhooks/whatsapp endpoints are how upstream carriers report delivery back to the platform — they're platform-internal and not something you configure; they exist so WeZend itself can populate the statuses you see via webhook_url and the message history API.
One platform. Every customer interaction.
Replace your patchwork of messaging APIs, CDP and automation tools with a single engagement platform built for scale.
No credit card required · EU data residency · 99.99% uptime SLA