Management API

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:

NameDescriptionDefault
limitPage size (max 100)50
cursorOpaque encrypted token from the previous response's nextCursor. Treat as a black box — do not parse or modify it
statusFilter 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:

statusMeaningWhat to do
availablePayload retrieved and decrypted. data holds the original body; contentType describes the format (always application/json today).Use data directly.
forbiddenThe workspace's plan doesn't include payload storage (Free, Starter).Upgrade to Pro or above to read payloads.
processingThe 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_foundThe 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.
errorTransient 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.