Webhooks API
Receive HTTP callbacks when async flow runs reach a terminal state — signed payloads, retries, and signing-secret rotation.
Base URL: https://api.noukai.xyz/api/v1
Use webhooks to be notified when an async flow run finishes. You opt in per request by passing a callbackUrl on the /jobs endpoint; Noukai POSTs a signed JSON payload to that URL when the run reaches a terminal state.
When webhooks fire
Webhooks fire only on async (/jobs) executions. Sync /execute and step-through /step ignore callbackUrl.
v1 ships two terminal events:
| Event | Fires when |
|---|---|
flow.completed | The async run finished successfully and result is available. |
flow.failed | The async run failed (runtime error, credits exhausted, or BYOK key rejected). |
Subscribing per request
Pass callbackUrl (and optionally callbackEvents) on POST /seq/{org}/{project}/{flow}/jobs:
| Field | Type | Required | Description |
|---|---|---|---|
callbackUrl | string | No | HTTPS URL to POST the event to. Must resolve to a public IP. Omit to disable webhooks for the run. |
callbackEvents | string[] | No | Subset of events to deliver. Defaults to all terminal events. Allowed values: flow.completed, flow.failed. |
callbackUrl must be HTTPS and must resolve to a public IP address. Private, link-local, and loopback addresses are rejected by Noukai's SSRF guard and the delivery is recorded as failed_permanent.
Delivery request
Noukai POSTs application/json to your URL with the following headers:
| Header | Description |
|---|---|
Content-Type | Always application/json. |
User-Agent | Noukai-Webhook/1.0. |
X-Noukai-Event | Event name (flow.completed or flow.failed). |
X-Noukai-Delivery | UUID for this delivery row. Stable across retries. Use as your idempotency key. |
X-Noukai-Timestamp | Unix seconds at signing time. Same value as the t= field inside X-Noukai-Signature. |
X-Noukai-Signature | HMAC-SHA256 signature (see Verifying signatures). |
Redirects are disabled — a 3xx response is recorded as a permanent failure.
Payload — flow.completed
Payload — flow.failed
| Field | Type | Description |
|---|---|---|
event | string | flow.completed or flow.failed. Matches X-Noukai-Event. |
executionId | string (UUID) | The run's execution ID — same value /jobs returned synchronously. |
flowId | string (UUID) | Flow that was executed. |
organizationId | string | Org that owns the flow. |
durationMs | integer | Wall-clock duration of the run, in milliseconds. |
occurredAt | string (ISO 8601 UTC) | When the run reached the terminal state. |
result | object | null | Present on flow.completed. The flow's structured output. |
errorMessage | string | Present on flow.failed. Human-readable failure summary. |
failureReason | string | Present on flow.failed. One of error, credits_exhausted, byok_rejected. |
Payload size limit
The body is capped at 256 KB. If a flow.completed payload would exceed the cap, the result field is replaced and the body is delivered as:
If you need full output for large runs, fetch it from GET /seq/{org}/{project}/{flow}/jobs/{executionId} using the executionId from the payload.
Verifying signatures
Every delivery is signed with HMAC-SHA256 over <timestamp>.<body> using your org's signing secret:
During a 24-hour grace window after rotation, the header includes a second slot signed with the previous secret:
A handler that holds a single customer secret should accept the request if either v1 or v2 matches.
Node example
Python example
Verify against the raw request bytes, before any JSON parsing or middleware that re-encodes whitespace. Re-serialized JSON will not match the signature.
Retries and at-least-once delivery
Delivery is at-least-once. Noukai makes up to 6 attempts per delivery, retrying transient failures with an exponential schedule:
| Attempt | When it fires |
|---|---|
| 1 | Immediately after the run terminates. |
| 2 | 1 minute after attempt 1 fails. |
| 3 | 5 minutes after attempt 2 fails. |
| 4 | 30 minutes after attempt 3 fails. |
| 5 | 2 hours after attempt 4 fails. |
| 6 | 12 hours after attempt 5 fails. |
If attempt 6 fails, the delivery is marked dead_letter and is not retried again.
| Response | Behaviour |
|---|---|
| 2xx | Success. |
| 3xx | Permanent failure — redirects disabled. |
| 408, 429 | Retried. |
| 4xx (other) | Permanent failure. |
| 5xx | Retried. |
| Network error / timeout | Retried. |
Timeouts: 5 s connect, ~20 s total.
The X-Noukai-Delivery UUID is stable across retries — including the rare case where Noukai successfully posted to you but failed to record the outcome internally. Always dedupe on this header before acting on a payload.
Signing secret management
Each org has one webhook signing secret. The secret is auto-created the first time you fetch or rotate it.
Get masked secret
Returns a masked preview. The full plaintext is never returned by this endpoint — by design. You only see the full secret once, in the rotation response. Available to any org member.
| Field | Type | Description |
|---|---|---|
secretPreview | string | First 10 chars plus a fixed mask. Safe to log. |
version | integer | Bumps on every rotation. |
createdAt | string | When the current secret was issued. |
rotatedAt | string | null | When the most recent rotation happened, if any. |
graceUntil | string | null | While set, deliveries include v2= signed with the previous secret. |
Rotate the secret
Admin-only. Atomically issues a new secret and returns the full plaintext exactly once. For the next 24 hours, deliveries are signed with both the new secret (v1=) and the previous secret (v2=), so you can roll your verifier without dropping events.
newSecret is shown once. Store it before you dismiss the response. If you lose it, rotate again.
Listing deliveries
Returns recent deliveries ordered newest-first. Available to any org member.
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | 1–200, default 50. |
before | string (ISO 8601) | Cursor — return deliveries created strictly before this timestamp. Pass back nextCursor to page. |
Response
| Status | Meaning |
|---|---|
pending | Row written; not yet attempted. |
in_flight | Worker is mid-attempt. |
succeeded | Endpoint returned 2xx. |
failed_retry | Retryable failure; another attempt is scheduled. |
failed_permanent | Non-retryable failure (3xx, non-408/429 4xx, SSRF block). |
dead_letter | All 6 attempts exhausted. |
Authentication
Both API keys and JWT tokens are accepted on these endpoints. Rotation additionally requires the user to be an organization admin or owner.
Error Responses
| Status | Description |
|---|---|
| 401 | Invalid or missing auth token. |
| 403 | Caller is not a member of the org (or not an admin, on rotation). |
| 404 | Organization not found. |
| 422 | Request validation error. |