Skip to main content
An outbound webhook endpoint is a public HTTPS URL that Senderz POSTs signed event payloads to. This page covers the three Public API operations that manage those endpoints, plus the delivery format and signature verification. All three use the X-Sender-Tenant + X-API-Key header pair and return the standard { success, data } envelope. The shapes below are the value of data.
The Public API has no update route. There is no PATCH and no activate or deactivate action. To change a URL or an event list, create a new endpoint and delete the old one. active is therefore read-only on this surface: a new endpoint is always created active, and only the Senderz app can pause one.

The endpoint object

string
required
UUID. Stable for the life of the endpoint. This is the value you pass to DELETE /public/webhooks/:id, and what the Make integration stores as its hook id.
string | null
required
The label you supplied, or null if you omitted it.
string
required
The delivery URL.
string[]
required
The event names this endpoint is subscribed to. ["*"] means every event.
boolean
required
Whether deliveries are attempted. Always true on creation. A paused endpoint returns false and is skipped at delivery time.
string
required
ISO 8601 timestamp.
signingSecret appears on exactly one response, the 201 from create. It is never present in the list response.

List endpoints

Takes no parameters. Returns a raw array of endpoint objects, newest first by createdAt. This route is not paginated.

Subscribe an endpoint

string
required
The delivery URL. Must be https, must carry no explicit port, must have a hostname containing a dot, and must resolve publicly. Maximum 1000 characters. See URL rules.
string[]
required
At least one event name. Each entry must be either the literal "*" or a lowercase dotted name matching ^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)+$. At least one dot is required, so order is rejected and order.completed is accepted.Names are not checked against the built-in vocabulary. Any name that matches the pattern is stored, which is how you subscribe to your own custom events.
string
Optional label, maximum 255 characters. Stored as null when omitted.
Returns 201 Created with the endpoint object plus signingSecret.
string
required
32 random bytes as a 64-character lowercase hex string, with no prefix. It keys the HMAC on every delivery from this endpoint.Returned once. It is absent from the list response, there is no reveal action and there is no rotate action. Store it before you close the response.
If the secret is lost, the only recovery is to create a replacement endpoint and delete this one. Point your receiver at the new endpoint before deleting the old one so nothing is dropped in between.

Subscribe to everything

Send the literal "*" as the only entry to receive every event with one endpoint.
A wildcard endpoint can also receive names that are not in the built-in vocabulary, including your own custom events. Ignore names your receiver does not recognise rather than erroring on them.

URL rules

The URL is resolved and checked when you create the endpoint, and again immediately before every delivery. It must be:
  • HTTPS. Plain http is rejected by validation.
  • On the default port. Any explicit port, including :443, is rejected.
  • A hostname containing a dot. Bare names such as localhost are rejected.
  • Publicly resolvable. The hostname is resolved and every returned address must be public. Loopback, private, link-local, carrier-grade NAT, multicast and unique-local addresses are all rejected, as is a hostname that does not resolve at all. If DNS returns both a public and a private address for the same name, the URL is rejected.
A rejection at creation is 400 with code: "webhook_url_blocked" and the message “A webhook endpoint must be a public HTTPS address on the default port.” The message is identical for every reason, so it does not indicate which rule you tripped.
400 Bad Request
Testing locally, put a public HTTPS tunnel in front of your machine and register the tunnel hostname. Because the URL is re-checked before each delivery, an endpoint whose DNS later points at a private address stops delivering.

Unsubscribe an endpoint

string
required
The endpoint id from create or list. Must be a valid UUID, or the request fails validation with 400 before the endpoint is looked up.
Returns 204 No Content with an empty body. The deletion is permanent and the signing secret goes with it. An id that does not belong to your workspace returns 404.
cURL

Discover event names

Returns the built-in vocabulary plus the custom events this workspace has actually observed, which is what you need before composing an events array. Use it to populate a picker instead of hardcoding names.
integer
default:"50"
1 to 100.
integer
default:"0"
Zero or greater.
Responds with the standard paginated shape. The 29 built-in events come first, in the order listed below, followed by this workspace’s custom events. Each item carries:
string
required
The event name to put in events.
string
required
Display label. Equal to value for built-in events.
string
required
system for a built-in event, custom_event for one this workspace has sent.
string[]
Property names seen on that custom event. Present on custom_event items only.

Event vocabulary

There are 29 built-in event names.
Requires a connected store. Which of these arrive depends on the platform.
order.completed, order.refunded, order.fulfilled, order.partially_fulfilled, order.cancelled, shipment.confirmed, shipment.out_for_delivery, shipment.delivered, checkout.started, cart.abandoned, contact.created, contact.updated, contact.unsubscribed, list.subscribed, list.unsubscribed, campaign.sent, email.opened, email.clicked, sms.delivered, sms.failed, review.request_ready, review.submitted, review.question, review.answer, loyalty.points_earned, loyalty.points_expiring, loyalty.tier_changed, loyalty.redemption, loyalty.referral

The delivery payload

Every delivery is a POST with Content-Type: application/json and three headers: The body always has the same three top-level keys:
deliveryId also appears in the X-Sender-Delivery header. The two are the same value.
data is not one fixed schema. Its keys depend on which part of the pipeline produced the event, so branch on event and tolerate keys you do not recognise.
externalId, orderId, amount, currency.
id, email, phone, firstName, lastName, tags, emailConsent, smsConsent, createdAt, updatedAt.
listId, listName, contactId.
Always carries contactId and occurredAt. The remaining keys vary with the source of the event and can include channel, messageId, providerMessageId and, for a send-time SMS rejection, reason.
Reviews: vendor, rating, productId, published, orderId. Loyalty: vendor, points, tier, rewardCode.

Verify the signature

Compute the HMAC-SHA256 of the raw request body, keyed by the endpoint’s signing secret, render it as lowercase hex, prefix it with sha256= and compare it against X-Sender-Signature in constant time. Two things break verification, both common. Read the body as raw bytes or a raw string before any JSON parsing, because re-serialising a parsed object changes the bytes and the digest with it. And compare with a constant-time function, not ==.

Worked example

Given this signing secret:
and this exact raw body:
the header must be:
Run your verifier against those three values before you go live. If it does not reproduce that digest, the mismatch is in how you read the body.

Delivery behaviour

Answer with any 2xx. Anything else is treated as a failure.
  • 5 attempts per delivery. Waits double from 5 seconds, so roughly 5s, 10s, 20s, 40s. After the fifth failure the delivery is dropped. There is no replay action.
  • 10 second timeout. A slower response is aborted and counts as a failed attempt.
  • Retried: any non-2xx status including 4xx, connection errors, and timeouts.
  • Not retried: a 3xx. A redirect is never followed and the delivery fails on the first attempt. Register the final URL.
  • Not retried: a URL that no longer passes the URL rules, and a paused or deleted endpoint. Deliveries already queued when an endpoint is paused are dropped, not held.
Retries reuse the same deliveryId, so deduplicating on it collapses a repeat of an event you already handled. Acknowledge quickly and do the work asynchronously: a receiver that processes inline and answers after 10 seconds is retried even though it succeeded. Full failure semantics are in Outbound webhooks.

Errors

Outbound webhooks guide

Per-event firing conditions, delivery visibility in the app, and troubleshooting a receiver that is not getting events.