Deliveries
Read delivery status, decrypted payloads, and attempt history programmatically
Once webhook events start flowing through your workspace, the Management API lets you inspect what happened with each delivery — its current status, how many attempts it took, and (on Pro and above) the original decrypted payload retrieved from object storage.
API base path: /management/v1/workspaces/{workspaceId}/deliveries
List an endpoint's deliveries
Returns deliveries for a single endpoint, paginated and newest-first. Data retention is 7 days — deliveries older than that are not returned.
curl "https://api.nahook.com/management/v1/workspaces/ws_abc/endpoints/ep_target001/deliveries?limit=50&status=failed" \
-H "Authorization: Bearer nhm_YOUR_TOKEN"Query parameters:
| Name | Description | Default |
|---|---|---|
limit | Page size (max 100) | 50 |
cursor | Opaque encrypted token from the previous response's nextCursor. Treat as a black box — do not parse or modify it | — |
status | Filter by pending, delivering, delivered, scheduled_retry, failed, or dead_letter | — |
Response:
{
"deliveries": [
{
"id": "del_aaa001",
"idempotencyKey": "order-1234",
"endpointId": "ep_target001",
"status": "delivered",
"totalAttempts": 1,
"firstAttemptAt": "2026-05-28T14:31:00Z",
"deliveredAt": "2026-05-28T14:31:00Z",
"nextRetryAt": null,
"hasPayload": true,
"createdAt": "2026-05-28T14:30:59Z",
"updatedAt": "2026-05-28T14:31:00Z"
}
],
"nextCursor": "eyJ0Ijoi..."
}nextCursor is an opaque encrypted token — treat it as a black-box string. Pass it as the cursor query param on your next request. When there are no more results, nextCursor is null. If a cursor is tampered with or the server's signing key has rotated, the next request returns 400 invalid_cursor and pagination must restart without a cursor.
Get a delivery
Returns the delivery's status and metadata. The endpoint returns HTTP 200 for any delivery the caller owns; a 404 means the delivery doesn't exist or it isn't owned by this workspace (we intentionally don't distinguish the two — that would let callers enumerate delivery IDs across workspaces).
curl https://api.nahook.com/management/v1/workspaces/ws_abc/deliveries/del_xyz \
-H "Authorization: Bearer nhm_YOUR_TOKEN"Response:
{
"id": "del_xyz",
"idempotencyKey": "order-1234",
"endpointId": "ep_target001",
"status": "delivered",
"totalAttempts": 1,
"firstAttemptAt": "2026-05-28T14:31:00Z",
"deliveredAt": "2026-05-28T14:31:00Z",
"nextRetryAt": null,
"hasPayload": true,
"createdAt": "2026-05-28T14:30:59Z",
"updatedAt": "2026-05-28T14:31:00Z"
}hasPayload reflects whether the original payload was stored in object storage at ingest time. It's an existence-level fact — a workspace that downgraded after the delivery will still see hasPayload: true, but will get payload.status: "forbidden" when requesting the body (see below).
Get a delivery with its payload
Pass ?include=payload to also receive the decrypted payload wrapped in an envelope. The endpoint stays HTTP 200; the envelope's status field carries the access-level reality.
curl "https://api.nahook.com/management/v1/workspaces/ws_abc/deliveries/del_xyz?include=payload" \
-H "Authorization: Bearer nhm_YOUR_TOKEN"Response (happy path):
{
"id": "del_xyz",
"idempotencyKey": "order-1234",
"endpointId": "ep_target001",
"status": "delivered",
"totalAttempts": 1,
"firstAttemptAt": "2026-05-28T14:31:00Z",
"deliveredAt": "2026-05-28T14:31:00Z",
"nextRetryAt": null,
"hasPayload": true,
"createdAt": "2026-05-28T14:30:59Z",
"updatedAt": "2026-05-28T14:31:00Z",
"payload": {
"status": "available",
"data": { "orderId": "ord_123", "total": 4200 },
"contentType": "application/json"
}
}Payload envelope status values
The envelope always has a status field. Five values are possible:
status | Meaning | What to do |
|---|---|---|
available | Payload retrieved and decrypted. data holds the original body; contentType describes the format (always application/json today). | Use data directly. |
forbidden | The workspace's plan doesn't include payload storage (Free, Starter). | Upgrade to Pro or above to read payloads. |
processing | The delivery is still in flight (status is pending or delivering); the object-storage write may not have completed yet. | Retry after a short delay. |
not_found | The delivery reached a terminal state but the payload was never stored. This happens for older deliveries from before a plan upgrade, or if retention pruned the payload. | Stop polling — the payload won't appear. |
error | Transient infrastructure failure fetching or decrypting the payload. | Retry with exponential backoff. |
In v1, data is the parsed JSON body and contentType is always application/json — the ingest API only accepts JSON today. When non-JSON ingest is supported in a future version, data will become a string and contentType will carry the original media type.
List delivery attempts
Each delivery may have one or more attempts (retries on failure). This endpoint returns the attempt history with response codes, latency, and error messages.
curl https://api.nahook.com/management/v1/workspaces/ws_abc/deliveries/del_xyz/attempts \
-H "Authorization: Bearer nhm_YOUR_TOKEN"Response:
[
{
"id": "att_001",
"attemptNumber": 1,
"status": "failed",
"responseStatusCode": 502,
"responseTimeMs": 142,
"errorMessage": "Bad gateway",
"createdAt": "2026-05-28T14:31:00Z"
},
{
"id": "att_002",
"attemptNumber": 2,
"status": "success",
"responseStatusCode": 200,
"responseTimeMs": 88,
"errorMessage": null,
"createdAt": "2026-05-28T14:31:30Z"
}
]Attempts are returned in chronological order (oldest first). responseTimeMs is wall-clock time from request start to response (or timeout). errorMessage is populated for failed attempts when the failure reason was a transport-level error (DNS, connection reset, timeout) rather than a non-2xx HTTP response.