Events
The
run.* events are fired once, without retry — pair them with polling GET /v1/runs/:id if your endpoint can miss deliveries. message.received is queued and retried.Delivery
Each delivery is an HTTPPOST with content-type: application/json and a 10-second timeout. Every delivery to your org carries the org-level secret in the x-sente-secret header — verify it with a constant-time compare before trusting the payload:
http(s) and resolve to public addresses. URLs pointing at loopback, private-range, link-local, or cloud-metadata addresses are rejected at registration (400) and re-checked before every delivery.
Create a webhook
POST /v1/webhooks
Idempotent per (url, identity scope): re-posting the same url and scope returns the existing subscription with its events replaced — safe to call on every app start.
201 (the only place the secret is returned by this API — store it):
400 with zod issues for a bad events value; 400 {"error": "webhook url not allowed: ..."} for a non-public URL; 404 {"error": "identity not found"} for a foreign identityId.
List webhooks
GET /v1/webhooks
200 (no secret in list responses):
Delete a webhook
DELETE /v1/webhooks/:id
204 with no body. 404 {"error": "not found"} otherwise.
Payloads
message.received
GET /v1/messages/:id.
run.blocked
Fired the moment a run parks for a human. Carries everything a notifier needs to route someone to the gate:actionUrl deep-links to the dashboard run page (live view + resume in one place). holdExpiresAt is when the run auto-fails BLOCKED_TIMEOUT if nobody acts — your notification can say how long the human has. The code values are listed on Runs.
run.completed / run.failed
run.failed has the same shape with "status": "failed", the failure code/detail set, and "result": null.