Create a webhook
Subscribe an HTTPS endpoint to this account’s events. The answer carries the signing secret once; it is not readable again, and POST /webhooks/{id}/rotate is how you get a new one.
WHAT ARRIVES. A POST per event to url, whose body is the event object exactly as the computer’s event socket (its events_url) frames it — type, at, computer, seq, cursor, source, data — byte for byte, with nothing added and nothing wrapped around it. cursor is the bridge back to the stream: a job woken by process.exited that wants everything since can open the computer’s events_url with since= that cursor added. computer says which machine, and every subscription may receive events from many. Two bodies are not events and lack fields: a gap (below) has no seq, and a test delivery (type: "webhook.test", from POST /webhooks/{id}/test) has neither seq nor cursor. data is left out of an event that has none. WebhookEvent in the OpenAPI document (its top-level webhooks) is all three as a schema.
HEADERS. content-type: application/json, user-agent: Mandala-Webhooks/1, and the three Standard Webhooks headers: webhook-id (the delivery id, unchanged across retries), webhook-timestamp (Unix seconds, the time of THIS attempt) and webhook-signature (v1, and base64 of HMAC-SHA256 over <id>.<timestamp>.<raw body>, keyed by the secret’s bytes after whsec_, base64-decoded). Any Standard Webhooks verifier library checks it; verify the RAW request bytes, never a re-serialised body. Refuse a webhook-timestamp more than 300 seconds from your clock, and remember each webhook-id you accept for at least that long — together those two close every replay, and a retry of a delivery you already accepted is then recognised rather than processed twice. mandala-subscription names the subscription and is NOT signed: route on it, never authorise on it.
ACKNOWLEDGE WITH A 2xx BEFORE DOING THE WORK. An attempt is cut at 10 seconds and counted as a failure; anything else — a non-2xx, a timeout, a refused connection, a TLS error, a redirect (never followed) — is retried: eight attempts over about fourteen hours (30 s, 2 min, 10 min, 30 min, 1 h, 4 h, 8 h), then the delivery is exhausted and visible in GET /webhooks/{id}/deliveries. Retries carry the same webhook-id and a fresh timestamp and signature. No ordering is promised: deliveries for one subscription run four at a time and retries interleave with new events, so order by seq per computer if you care. At least once, never silently dropped.
AN ENDPOINT THAT KEEPS FAILING IS DISABLED: when a delivery runs out of attempts and nothing has been accepted for 24 hours, enabled becomes false with disabled_reason: "failing", pending deliveries are dropped, and PATCH {"enabled": true} starts it again. The 24 hours count from the later of the last 2xx and when the subscription was last enabled: created enabled, enabled again with PATCH, or turned back on by a plan. So an endpoint enabled again gets a fresh 24 hours, not the remainder of a day that began at its last 2xx.
WHEN THE PLAN ENDS. An account that moves to a plan without webhooks has every enabled subscription disabled with disabled_reason: "plan". Deliveries already queued are still made, and so is one more kind for the next 24 hours: computer.stopped, so you hear about the computers stopped because the plan ended (subject to the subscription’s own events and computers). Nothing else is queued. Until the account has a plan again, a new url, enabled: true and a test are 402; reading, filters, rotating and deleting still work. Choosing a plan that includes webhooks enables them again, oldest first, as many as it allows, each starting fresh as a PATCH would; the rest stay disabled with disabled_reason: "plan".
GAPS. If the hypervisor holding a computer was unreachable for longer than its event journal holds, you get a {"type": "gap", "computer": …} delivery whose data.detail says events were lost and whose cursor is the computer’s own — the same frame the socket sends, and it arrives whatever events filters on. Reconcile with a listing rather than assume nothing happened.
Every paid plan allows ten subscriptions per account; the eleventh is a 409 naming the cap. The count is the whole account’s, so an API key confined to a workspace can meet the cap while GET /webhooks shows it fewer than ten.
Requires the member role or higher.
Request
- Base URL: https://app.mandala.computer/api/v1
- URL: https://app.mandala.computer/api/v1/webhooks
- Auth: HTTP bearer
Request body
Response
The subscription, with its secret — shown once.
Changes
No recorded changes to this operation across all 1 revision of this API.