Webhooks
More ways to use this page
Create a webhook
POST/v1/
TypeScript sendora
Python sendora
Key sk_… a server key
Registers a URL to receive events. The answer carries the signing secret once; every delivery is signed with it (HMAC-SHA256 over the body) so the receiver can verify Sendora sent it. A second secret can be created later to roll the first one.
Request body
urlstringrequiredAn https URL on a public host, without credentials or a fragment.
eventsarray of "delivered" | "bounced" | "deferred" | "spam_complaint" | "unsubscribed" | "cap_ warning" | "cap_ reached" | "inbound", at least 1 The events to deliver; every event when left out.
streamIdstring (uuid)Limits the message events to one stream of the server; every stream when left out. Cap events concern the server and arrive either way.
inboundContent"full" | "reference"How the inbound event carries a received message: full sends the parsed message with the event; reference sends ids and sizes only, for you to fetch. full when left out.
{
"url": "https://example.se/hooks/sendora",
"events": [
"delivered",
"bounced",
"spam_complaint"
]
}Responses
201The new webhook with its signing secret, shown this once.
webhookIdstring (uuid)requiredurlstringrequiredWhere the events are posted.
eventsarray of "delivered" | "bounced" | "deferred" | "spam_complaint" | "unsubscribed" | "cap_ requiredwarning" | "cap_ reached" | "inbound" streamIdstring (uuid) or nullrequiredThe one stream whose message events it receives; null for every stream.
enabledbooleanrequiredFalse while switched off in the dashboard; nothing is queued for it then.
inboundContent"full" | "reference"requiredHow the inbound event carries a received message: the parsed message, or a reference.
createdAtstring (date-time)requiredsecretsarray of objectrequiredThe live secrets, oldest first; every delivery is signed with each of them, the newest first in Sendora-Signature.
secretIdstring (uuid)requiredcreatedAtstring (date-time)required
secretstringrequiredThe signing secret, shown this once.
Example (20 lines)
{
"webhookId": "7c1e4d2a-0b9f-4a3e-8d6c-5e2f1a9b8c70",
"url": "https://example.se/hooks/sendora",
"events": [
"delivered",
"bounced",
"spam_complaint"
],
"streamId": null,
"enabled": true,
"inboundContent": "full",
"createdAt": "2026-09-15T12:00:00.000Z",
"secrets": [
{
"secretId": "4a5b6c7d-8e9f-4a0b-8c1d-2e3f4a5b6c7d",
"createdAt": "2026-09-15T12:00:00.000Z"
}
],
"secret": "whsec_9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c"
}Errors
Every error answers error, the code, and message, a sentence for a person. A code that adds fields is shown in full below the table.
| Code | Status | Meaning |
|---|---|---|
invalid_ | 400 | The body, the query or a header does not match what the route takes. |
unauthorized | 401 | The key is missing, malformed or revoked. |
wrong_ | 403 | The key is of the other kind: a server key (sk_) where an account key (ak_) is needed, or the reverse. The message names the kind the operation takes. |
webhook_ | 409 | The server already has a webhook for that URL. |
stream_ | 422 | The streamId names no stream of this server. |
invalid_request
issuesarray of objectOne entry per invalid field; absent when a header is wrong.
pathstringrequiredThe field, dotted, such as to.0.email; empty when the whole body is wrong.
messagestringrequired
Example
{
"error": "invalid_request",
"message": "The request is invalid: to.0: Invalid email address",
"issues": [
{
"path": "to.0",
"message": "Invalid email address"
}
]
}webhook_exists
webhookIdstring (uuid)requiredThe id of the webhook the server already has for that URL.
Example
{
"error": "webhook_exists",
"message": "This server already has a webhook for that URL.",
"webhookId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}