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
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.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.
Subscribe to everything
Send the literal"*" as the only entry to receive every event with one endpoint.
URL rules
The URL is resolved and checked when you create the endpoint, and again immediately before every delivery. It must be:- HTTPS. Plain
httpis rejected by validation. - On the default port. Any explicit port, including
:443, is rejected. - A hostname containing a dot. Bare names such as
localhostare 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.
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
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.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
events array. Use it to populate a picker instead of hardcoding names.
integer
default:"50"
1 to 100.
integer
default:"0"
Zero or greater.
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.- Commerce
- Contacts and lists
- Messaging
- Reviews and loyalty
Requires a connected store. Which of these arrive depends on the platform.
All 29 names, flat
All 29 names, flat
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.referralThe delivery payload
Every delivery is aPOST 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.
Commerce data
Commerce data
externalId, orderId, amount, currency.Contact data
Contact data
id, email, phone, firstName, lastName, tags, emailConsent, smsConsent, createdAt, updatedAt.List data
List data
listId, listName, contactId.Messaging data
Messaging data
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 and loyalty data
Reviews and loyalty data
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 withsha256= 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:Delivery behaviour
Answer with any2xx. 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.
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.

