Webhooks let your app react to run lifecycle events without polling. When an event fires, Spine POSTs a signed JSON envelope to a URL you configured, and you verify it with a shared secret. Pair webhooks with polling if you want both — the delivery tells you when a run is ready, andDocumentation Index
Fetch the complete documentation index at: https://docs.getspine.ai/llms.txt
Use this file to discover all available pages before exploring further.
GET /v1/run/{run_id} gives you the full shape on demand.
Webhook endpoints are configured per workspace in the Spine developer portal. A single endpoint can subscribe to one or more event types, or to
* for everything.Event types
| Event | Fires when |
|---|---|
run.started | Immediately after POST /v1/run creates the run. |
run.completed | The run reaches terminal status completed. Includes final_output and artifacts. |
run.failed | The run reaches terminal status failed. Includes the error message. |
webhook.ping | Fired by Send test event in the portal — use it to exercise your verifier. |
Event envelope
Every delivery has the same top-level shape.| Field | Type | Notes |
|---|---|---|
id | string | evt_<uuid>. Unique per delivery — use for idempotency. |
type | string | One of the event types above. |
created | int | Unix seconds when the event was generated. |
livemode | bool | true in production, false in development / staging. |
api_version | string | Envelope schema version. Bumped on breaking changes. |
data.object | object | The payload. For run.* events, mirrors GET /v1/run/{run_id}. |
Request headers
Spine-Signature is t={unix_ts},v1={hex} — timestamp and HMAC-SHA256 over {t}.{raw_body}. Spine-Event-Id equals event.id in the body; persist it to dedupe. Spine-Event-Type is redundant with event.type but handy for routing before JSON parse.
Verify the signature
Given thewhsec_... secret you stored when you created the endpoint, the raw request body, and the Spine-Signature header, verification is five steps:
- Parse the header. Split on
,to extractt(unix seconds) and everyv1=value. - Check freshness. Reject if
abs(now - t) > 300— blocks replayed deliveries. - Rebuild the signed payload. Literally
{t}.{raw_body}: the header’stvalue, a single., then the raw request body bytes. Not the parsed JSON — frameworks may reorder keys and invalidate the signature. - Recompute the HMAC.
HMAC-SHA256(secret, signed_payload)as a lowercase hex digest. - Constant-time compare against any
v1value from the header. Usehmac.compare_digest(Python) orcrypto.timingSafeEqual(Node) — never==.
Test your setup
Before wiring real events to your verifier, send a ping and confirm it parses cleanly.- In the Spine developer portal, open your webhook endpoint and click Send test event. This fires a
webhook.pingdelivery through the real pipeline — signed, headered, same shape as production events. - Confirm your endpoint returned
2xxin the portal’s delivery log. A non-2xx means your verifier rejected a legitimate Spine signature; inspect the delivery’s response body for your error message. - Click Resend on the delivery to re-fire it as you iterate.
- Body was re-parsed before hashing. Hash the bytes you received, not the re-serialized JSON. Express needs
express.raw(); FastAPI needsawait request.body()on theRequestobject. - Wrong secret. After rotation, the previous secret stops signing immediately — update the value in your app before the next delivery.
- Clock skew. If your server drifts more than 5 minutes from Spine’s clock, every delivery will fail the freshness check. Run NTP.
- Non-constant-time compare. Fine functionally, but leaks timing information to attackers. Use the platform primitive.
Responding
Return any2xx status within 10 seconds to mark the delivery as succeeded. Anything else — non-2xx, timeout, network error — is recorded as failed in the delivery log. Do heavy work asynchronously: acknowledge fast, process later.
Idempotency
Two situations can look like duplicates:- Resend. Clicking Resend in the portal clones a past delivery and fires a fresh one with a new
Spine-Event-Id. - Repeat trigger. If a run transitions through a terminal state more than once (rare), each transition issues a new delivery with a new
Spine-Event-Id.
Spine-Event-Id alongside the downstream effect, and skip if you’ve seen it.
Secret management
The fullwhsec_... secret is shown exactly once when you create or rotate an endpoint. Store it in a secret manager or your .env — after the reveal, the portal shows only a masked form like whsec_••••abcd.
Rotating invalidates the old secret immediately. If multiple consumers share a secret, update your verifiers before rotating.
Notes
- Deliveries are single-attempt in v1. Transient failures are recorded and can be re-fired with Resend in the portal. Automatic retries with exponential backoff are planned.
- Use
*in the events list to subscribe an endpoint to every event type, including ones added later. - HTTPS is enforced in production.