POST that Nexio sends to your server when a run reaches a terminal status, or when Nexio attaches a correction to a finished run. Use webhooks to learn about results without polling. They are delivered at least once and in no guaranteed order, so treat each delivery as a signal. GET /api/v1/runs/{run_id} is the source of truth for a run; if a webhook and the run disagree, the run wins. Corrections are the exception: after Nexio records a correction, GET keeps returning the original output (see run.superseded).
Webhooks are one of three event mechanisms on the platform. Events overview explains how they differ from platform events and inbound events.
Credential. Every route on this page accepts an organization API key or a capability-scoped key (nxsk_v1_...) that holds webhooks:manage. A scoped key without it gets 403 insufficient_capability. An endpoint belongs to the environment of the key that created it, and you cannot choose another. See Authentication and Environments.
How it works
- You register an HTTPS endpoint and the events it wants. Nexio returns a signing secret once.
- When a run in that environment reaches a terminal status, Nexio creates one delivery for each active endpoint that lists the event.
- Nexio posts the event body with
X-Nexio-*headers and an HMAC-SHA256 signature over the exact bytes. - Your receiver verifies the signature, records
X-Nexio-Delivery, and answers2xxwithin 10 seconds. - If the receiver fails, Nexio retries on the endpoint’s retry policy. A delivery that cannot succeed ends in
dead_letter, and you can resend it after you fix the receiver.
Create an endpoint
POST /api/v1/webhooks registers an endpoint and returns its signing secret. The secret is shown only in this response. Store it in your secret manager before you do anything else.
201 Created:
Unknown fields are refused with
400 invalid_request. An environment holds at most 10 active endpoints; creating or re-enabling an eleventh active endpoint gets 409 webhook_limit_exceeded. The secret is whsec_ followed by 64 hex characters.
To change an endpoint later, call Update webhook endpoint with any of url, events, description, active, auth_token, or payload_mode. Send null or an empty string to clear description or auth_token. Setting active to false cancels the endpoint’s pending deliveries. A new url applies to new deliveries only, because each delivery keeps the URL it was created with, including when you resend it. You can also manage endpoints in the portal under Settings, then Webhooks.
Event catalog
An endpoint receives only the events it lists. There are four.
What a
run.superseded delivery carries depends on your organization’s delivery setup. In one setup, each correction is its own event, and a full body carries the corrected output. In the other, the event is keyed to the run, a full body carries the run’s stored (original) output, and a later correction of the same run sends no new delivery to an endpoint that already has one. Treat run.superseded as a signal that a correction exists. Webhook events has a full example body and the trigger rules for each event.
Envelope
Every delivery body has the same envelope. The machine-readable schema isWebhookEvent in the OpenAPI document.
Payload modes
Each endpoint has apayload_mode. The envelope, headers, and signature are the same in both modes. Only data.run changes.
A thin body is smaller than 1024 bytes. Nexio cuts an
error longer than 256 bytes to 256 characters plus a short truncation note, and replaces it with a fixed message if the body would still reach 1024 bytes. Fetch the run for the full error. A full body has no fixed ceiling, because it carries the whole run output.
A thin run.completed delivery:
Headers
Nexio does not follow redirects. A
3xx answer is a permanent failure.
Verify each delivery
The signature is a hex HMAC-SHA256, keyed with the endpoint secret, over this exact byte string:- Read the raw request bytes before any JSON parsing.
- Split
X-Nexio-Signatureon commas. Take the onetvalue and everyv1value. Refuse a header with twotvalues. - Require
tto equalX-Nexio-Timestamp, and require it to be decimal digits. - Refuse the delivery when
abs(now - t)is more than 300 seconds. Exactly 300 is accepted. - Compute the HMAC with every secret you currently accept (two during a rotation) and compare against every
v1value in constant time. Accept if any pair matches. - Record
X-Nexio-Deliverywith an atomic insert. See Deduplicate. - Parse the JSON only after the signature passes.
Reference verifiers
The Python, JavaScript, and Go verifiers below are the files Nexio’s own delivery tests run against the signature test file, a JSON list of signed requests with the expected accept or refuse result for each. CI checks that the published copies match the tested source byte for byte. The TypeScript tab is a typed port of the JavaScript verifier for Node 20 and later with the same checks; it is not one of the tested or byte-checked files. Each keeps seen delivery IDs in memory to show the deduplication contract; replace that set with a durable, shared store before production.Wire a verifier into a receiver
These receivers use the reference verifiers above. They read the raw body, verify, answer2xx for a duplicate, and hand the work to a queue so the response goes out well inside the 10 second timeout.
Deduplicate on X-Nexio-Delivery
Delivery is at least once. The same delivery can arrive more than once, for example when your receiver committed its work but the response timed out. X-Nexio-Delivery stays the same across every retry and every resend of one delivery, so it is the key to deduplicate on.
- Insert the delivery ID into a table with a unique constraint before you apply side effects, in the same transaction when you can.
- When the insert conflicts, answer
2xxand do nothing else. An error answer makes Nexio retry a delivery you already handled. - Do not deduplicate on the body
idalone. Two endpoints receive the same eventidwith different delivery IDs.
run.superseded delivery can arrive before the run.completed for the same run. Compare with the run you already stored before you overwrite anything. GET /api/v1/runs/{run_id} returns the original output, not the correction.
Retry policies
Every endpoint has a retry policy, fixed when the endpoint was created. New endpoints getstandard-v3. The receiver timeout is 10 seconds on every policy. An attempt that has not answered in 10 seconds counts as a transport failure.
Without
Retry-After, standard-v3 gives up after about 3.3 days (about 4 days at maximum jitter), and standard-v2 after about 16 hours at maximum jitter.
How Nexio classifies each answer:
On
standard-v3 and standard-v2, a 429 or 503 with a valid Retry-After (seconds or an HTTP date) replaces the base wait when it is longer, capped at 12 hours per retry. Jitter applies after that. legacy-v1 ignores Retry-After.
Permanent failures
A permanent failure ends the delivery indead_letter on that attempt. It does not use the rest of the attempt budget. A receiver that answers 400, 401, 403, or 404 by mistake, or a load balancer that redirects, dead-letters every delivery on the first try. If your receiver is briefly unable to accept work, answer 503 (with Retry-After if you know when to come back) so Nexio retries.
Delivery states
Each delivery has one status. This is the only place the model is defined; other pages link here.
Each attempt is also recorded with its own outcome (
success, retry, dead_letter, permanent_failure, or late_result_dropped), status code, and duration. Read them with Get webhook delivery. The API never returns the signed body bytes or your receiver’s response body.
Automatic disable
Each time a delivery ends indead_letter, Nexio looks at the endpoint’s 10 most recent finished deliveries (success or dead_letter), counting only those that finished since the endpoint was last changed (created, updated, re-enabled, or its secret rotated). If there are 10 and all are dead_letter, Nexio deactivates the endpoint. It sets active to false, deactivated_by to system, and deactivated_reason to a message that names the delivery that tipped it, and it cancels the endpoint’s pending deliveries. Nexio skips the check while another delivery to the endpoint is in_progress; the next dead letter checks again.
The portal shows the reason on the endpoint page. To recover:
- Fix the receiver.
- Send a test delivery from the portal and confirm it passes.
- Set
activetotruein the portal or with Update webhook endpoint. Re-enabling clears the deactivation fields. - Reconcile the runs that finished while the endpoint was off by polling, and resend any
dead_letterdeliveries you need.
Rotate the secret
POST /api/v1/webhooks/{endpointID}/rotate-secret returns a new secret once. The old secret keeps working for an overlap that Nexio sets from the environment’s kind. You cannot change it.
During the overlap every delivery carries two
v1 values, one per secret. Accept both secrets until previous_secret_expires_at, then drop the old one.
200 OK:
previous_secret_expires_at. The new secret exists only once rotate returns it. During the overlap each delivery carries two v1 signatures, one per secret, so your receiver keeps verifying with the old secret until you install the new one.
Resend a dead-letter delivery
After you fix the receiver, find the deliveries that dead-lettered and resend them.200 OK:
- It works only on a
dead_letterdelivery whose endpoint is active and not deleted. Anything else, including an unknown delivery ID, gets409 delivery_not_resendable. - It keeps the delivery ID, so your deduplication still recognizes it, and it sends the same body bytes that were stored when the delivery was created. Each attempt is signed again with a fresh timestamp and the endpoint’s current secret.
- It posts to the URL stored on the delivery. If you fixed the receiver by changing the endpoint’s
url, a resend still goes to the old URL; reconcile those runs by polling instead. - It starts a new generation:
resend_generationgoes up by one,attempt_countgoes back to 0, andnext_attempt_triggerbecomesmanual_resend. - The new generation has the delivery’s own attempt limit, the
max_attemptsit already shows (8 onstandard-v3). A resend does not switch the delivery to a different policy. - Nexio records which key asked for the resend.
Send a test delivery
The portal can send a test delivery to an endpoint: open Settings, then Webhooks, open the endpoint, and go to its Settings tab. In Send test delivery, pick Small (about 1 KB) or Large (about 16 KB; the body is at least 16,000 bytes, to check that your gateway accepts large bodies), then choose Send. A test delivery is arun.completed event with data.run.output.test set to true and the header X-Nexio-Test: true. It is signed like a real delivery, with the same headers, so it proves your verification. It creates no delivery record and is not retried. Test sends are limited to 6 per minute per organization on each Nexio API server. There is no public API route for test deliveries.
Verify a test delivery normally in your receiver, then skip business side effects when X-Nexio-Test is true.
Reconcile by polling
Webhooks tell you that something changed. Polling tells you what is true now. Keep everyrun_id you submit, and reconcile with GET /api/v1/runs/{run_id} when:
- a delivery is in
dead_letterorcancelled, - the endpoint was deactivated for any period,
- a run you submitted has produced no delivery after your expected run time,
- you receive a
thinbody and need the output.
GET keeps returning the original output after Nexio records one.
See Runs for polling and statuses.
Platform event deliveries to the same endpoint
Nexio can also configure an automation for your organization that posts platform events to one of your registered webhook endpoints (thedeliver_webhook effect, described in Automations). Those deliveries use the same headers and the same signature scheme, so the verifiers above work unchanged. The body is different:
A simple rule: a body with a
data.run object is a run webhook; a body with dispatch_id is a platform event delivery. Deduplicate both on X-Nexio-Delivery. is_replay is true when the delivery comes from a replay (Nexio re-running the automation) rather than from the event’s first dispatch.
Limits
Common errors
The full error envelope is described in Errors.
Webhook events
Trigger rules and a full example body for each event.
Runs
Run statuses, polling, and the run object inside every delivery.
Create webhook endpoint
The endpoint reference for every webhook route.
Events overview
How webhooks relate to platform events and inbound events.