What is sent and how it is retried
The request to your endpoint (method, headers, body), the states of a delivery, the retry schedule, and disabling.
5 minute readWebhooksUpdated: 10/25/2026
On this page
Before you start
- For
- Anyone who implements a receiver or investigates a missing delivery.
- Prerequisite
- For the delivery log in the portal, the Owner or Manager role. For redelivering, the Owner role.
The request your endpoint receives, the states of a delivery, the retry schedule, and disabling, for reference when you build and operate a receiver.
The request to your endpoint
Every delivery is a POST to the address of the endpoint. The body is the payload of the event as JSON, serialized compactly (without spaces and line breaks). Only the payload travels. The time of the event, its identifier, and the trigger are not in the body.
| Component | Value |
|---|---|
| Method | POST |
Content-Type |
application/json |
X-Signature |
HMAC-SHA256 over the raw body, with the signing secret of the endpoint, base64url encoded |
X-Event-Type |
The event type, for example composition.scene.published |
X-Delivery-Id |
The ID of the delivery (whd_ and 26 characters). A retry carries the same one |
| Body | The payload of the event as JSON, the fields from the event tables |
| Timeout | 10 seconds for connection and response |
This is what a delivery of the event composition.scene.published looks like (body wrapped for readability, in reality it arrives on one line):
POST /hooks/signage HTTP/1.1
Host: warenwirtschaft.example.com
Content-Type: application/json
X-Signature: 3q2-7wE5c1nXm0Zk8YbT4vLpQ9sRd6HfJgUaWi2xO7A
X-Event-Type: composition.scene.published
X-Delivery-Id: whd_01KXZACW98SENDNG0000000001
{
"sceneId": "scn_01KJPX8NH0SZENE00000000001",
"tenantId": "tnt_01KERPXZM0ADERAPTHEKE00001",
"sceneVersionId": "scv_01KV5K0JE0VERSN00000000003",
"versionNumber": 4,
"contentHash": "b2f80a41d76c395ee0138b5a4c9d27f6103e8ab7592c4df081a63be95d7c024f",
"appVersionPins": {},
"publishedById": "usr_01KFB7QPW0TEAM000000000002",
"at": "2026-07-20T08:31:05.000Z"
}
The expected response
| Your response | Effect |
|---|---|
| Status 2xx within 10 seconds | The delivery counts as delivered. The failure counter of the endpoint resets to zero |
| 5xx, 408, or 429 | Temporary failure: retry according to the schedule below |
| No response within 10 seconds, connection error | Like a temporary failure: retry |
| Any other 4xx | Permanent failure: no retry, the delivery has failed for good |
On a failure, the response text is stored truncated (at most 4 kB) in the delivery log. On a successful delivery, only the status code is recorded. A short, descriptive text helps when you check later, but it is not required.
Delivery states
| State in the API | In the portal | Meaning |
|---|---|---|
pending |
Pending | Sending is in progress or imminent |
delivered |
Delivered | Your server responded with 2xx. deliveredAt is set |
failed |
Failed | Temporary failure. nextRetryAt gives the next attempt |
dead |
dead |
Failed for good: fifth failure or a 4xx response. deadAt is set |
The path is pending → delivered or failed. From failed, every further attempt leads back through pending to delivered, failed, or dead. For the state dead, the portal has no label of its own, and the delivery log shows the value itself. The label Retrying exists in the portal but is not assigned to any state.
Retry schedule
The platform retries only on 5xx, 408, 429, timeouts, and network errors. There are at most five attempts per delivery. The wait after the nth failure is 1 minute · 2ⁿ, with ±20% jitter. A job that runs every five minutes picks up due retries, up to 100 per run, so the actual wait is up to five minutes longer than the target value.
| Attempt | Wait after failure | On another failure |
|---|---|---|
| 1 | 2 minutes ±20% | Attempt 2 |
| 2 | 4 minutes ±20% | Attempt 3 |
| 3 | 8 minutes ±20% | Attempt 4 |
| 4 | 16 minutes ±20% | Attempt 5 |
| 5 | — | Failed for good (dead) |
A 4xx response other than 408 and 429 ends the delivery immediately, regardless of the attempt counter.
Redeliver by hand
In the delivery log of the endpoint, every delivery with the status Failed has the Retry button. For a delivery in the state dead, the portal shows no button. You trigger a redelivery through the API:
curl -X POST "$SZENERA_API/v1/webhooks/whk_01KPR20K40WEBHK00000000001/deliveries/whd_01KXZACW98SENDNG0000000001/retry" \
-H "Authorization: Bearer $TOKEN"
The response (status 201) is the delivery with the outcome of the new attempt. The new attempt is signed with the currently valid secret of the endpoint and carries the same X-Delivery-Id. If the endpoint is inactive, nothing is sent, and the response carries the delivery unchanged. The API does not check the state: even a delivery that was already delivered can be sent again this way. Your receiver then sees the same ID a second time.
Disabling an endpoint
Every failure raises the consecutiveFailures counter of the endpoint, and every success resets it to zero. If the counter is above 20, the endpoint becomes inactive (active: false), and the platform records notifications.webhook.endpoint.disabled. An hourly job makes up for what the counter missed at sending time. An inactive endpoint receives no new deliveries. In the portal it shows Inactive with the note “last failure …”. To turn it back on, use Activate or PATCH /v1/webhooks/{id} with {"active": true}. This does not take effect immediately: for each event type, the platform remembers for up to an hour whether an active endpoint has subscribed to it. After activating, or after adding events, it can therefore take up to an hour until the first delivery of a newly subscribed type arrives. Newly created endpoints take effect immediately.
The delivery log through the API
curl "$SZENERA_API/v1/webhooks/whk_01KPR20K40WEBHK00000000001/deliveries?limit=50" \
-H "Authorization: Bearer $TOKEN"
The response is a page with items and nextCursor (if there is more). The next call gets cursor=<nextCursor>. limit is between 1 and 200, and 50 if omitted.
{
"items": [
{
"id": "whd_01KXZACW98SENDNG0000000001",
"tenantId": "tnt_01KERPXZM0ADERAPTHEKE00001",
"endpointId": "whk_01KPR20K40WEBHK00000000001",
"eventType": "composition.scene.published",
"payloadSignature": "3q2-7wE5c1nXm0Zk8YbT4vLpQ9sRd6HfJgUaWi2xO7A",
"status": "delivered",
"attemptCount": 1,
"responseCode": 200,
"responseBody": null,
"nextRetryAt": null,
"dispatchedAt": "2026-07-20T08:31:05.000Z",
"deliveredAt": "2026-07-20T08:31:06.000Z",
"deadAt": null,
"createdAt": "2026-07-20T08:31:05.000Z"
}
],
"nextCursor": "d2hkOjIwMjYtMDctMTk"
}
| Field | Meaning |
|---|---|
id |
ID of the delivery, the same as in X-Delivery-Id |
endpointId, tenantId |
Endpoint and organization |
eventType |
The event type |
payloadSignature |
The signature that was sent as X-Signature |
status |
pending, delivered, failed, or dead |
attemptCount |
Number of attempts so far |
responseCode, responseBody |
Response from your server. responseCode is null if there was none. responseBody is filled only for failed attempts (truncated to 4 kB) and after a successful delivery is null or still carries the text of the last failed attempt |
nextRetryAt |
Next scheduled attempt, only for failed |
dispatchedAt, deliveredAt, deadAt, createdAt |
Times of the last send, the delivery, the final failure, and creation |
The body that was sent is not in the list.
Permissions
| Action | Permission | Roles |
|---|---|---|
| Read the delivery log | webhook.delivery.list |
Owner, Manager |
| Redeliver | webhook.delivery.retry |
Owner |
These permissions apply to signed-in accounts. An API key (ak_…) cannot carry them. The calls need the access token of a sign-in, see Authentication.