Webhooks
Rather than polling the API, get notified.
Rather than polling the API, get notified.
The four events
| Event | When it fires | data |
|---|---|---|
model.trained | a version has just been trained and serves predictions | model_id, version, target, task, n_train, metrics |
model.stale | enough outcomes have piled up, a refresh would help | model_id, pending_outcomes, hint |
model.refreshed | a refresh produced a new version | model_id, version, outcomes_applied |
credits.low | your credit balance fell below the alert threshold, the same signal as credits_low in GET /v1/usage | credit_balance |
Every event shares the same envelope:
{
"id": "evt_example",
"type": "model.refreshed",
"created_at": "2026-10-11T12:00:00.000Z",
"workspace_id": "ws_example",
"data": { "model_id": "mdl_example", "version": 2, "outcomes_applied": 12 }
}The event payload page details each schema.
Configure a destination
A workspace admin creates the destination on the Webhooks page of the Spavik console, with an HTTPS URL. The signing secret is shown once, at creation: store it in your receiver's secret manager and load it at runtime. Never put it in your source code.
Verifying the signature
Every delivery is signed. Verify it before doing anything with the content: without that check, anyone who knows your URL can tell you anything.
Each request carries a Spavik-Signature header:
Spavik-Signature: t=1760184000,v1=5f2b...e91ctis the signing time, in Unix seconds.v1is the hex HMAC-SHA256 oft, a dot, then the raw request body, keyed with the webhook secret as a UTF-8 string.- Compare signatures in constant time, and reject a
tmore than 300 seconds away from your clock: this blocks replaying an intercepted call. - Each attempt is signed again with a fresh
t;event.idand the payload stay the same.
The Python SDK and the
TypeScript SDK both provide verify, which
does all of this and returns the decoded event.
The bytes, not the object
The signature covers the bytes as received. A body parsed into JSON and re-serialised does not produce the same signature, even with exactly the same data. Read the raw body before a JSON parser transforms it.
Acknowledge delivery
Return any 2xx response once the event is stored durably, then process it in your application. Delivery is at least once:
- Each attempt waits 5 seconds for your response.
- Network errors, timeouts, HTTP 408, 429 and 5xx are retried, up to 8 attempts,
with exponential delays from 30 seconds capped at 1 hour. A
Retry-Afterfrom your receiver is honored up to 24 hours. - Any other non-2xx response is final, and redirects are not followed.
These are the default values. Deduplicate on event.id so that a second
delivery of the same event does not repeat a business action.
Events can arrive out of order. For a given model, compare data.version with
the version you already hold, or read the current state with
GET /v1/models/{id} rather than trusting the order of arrival.
Disabling a webhook or changing its URL cancels its pending deliveries.
How is this guide?
Last updated on