Reference
Webhooks
Every change to a simulation - from your requests, the behaviour plan or a fast-forward - emits an event. Webhooks deliver them to you, signed.
Registering
Add a webhook in the dashboard or with POST /v1/simulations/{id}/webhooks. Filter with exact event names (task.created), prefixes (task.*) or everything (*). The response includes the signing secret, which starts whsec_.
- Targets must be public
httpsURLs. Private, loopback, link-local and cloud metadata addresses are refused, and re-checked when each connection is made. - Redirects are not followed. Respond with any 2xx within 10 seconds.
- Your plan sets how many webhooks each simulation can have.
Request format
POST /hooks/slurry HTTP/1.1
Content-Type: application/json
User-Agent: Slurry-Webhooks/1.0 (+https://slurry.io)
Slurry-Event: task.updated
Slurry-Delivery: 9b2d6c1e-3f7a-4e0b-8a55-1c4f2e7d9a10
Slurry-Signature: t=1790327472,v1=5f1c0b6e2a...{
"id": "e3b1f4a2-7c9d-4d6e-b8a1-2f5c6d7e8f90",
"type": "task.updated",
"created": "2026-09-25T09:41:12.000Z",
"data": {
"collection": "tasks",
"id": "1204417763512",
"object": {
"gid": "1204417763512",
"name": "Invoice sync drops line items over 250",
"status": "in_review",
"assignee": { "gid": "1204417760001", "name": "Priya Raman" },
"modified_at": "2026-09-25T09:41:12.000Z"
}
}
}data.object is the record as your code would see it from the API. On deletes it may be null, so use data.id. Use Slurry-Delivery or id to make your handler idempotent: a retry carries the same delivery id.
Verifying signatures
Slurry-Signature is t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256(secret, t + "." + raw body). Compute it over the raw bytes you received, compare in constant time, and reject timestamps more than five minutes old to stop replays.
import crypto from 'node:crypto'
// Use the raw request body, exactly as received, before any JSON parsing.
export function verifySlurry(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const t = Number(parts.t)
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const a = Buffer.from(expected, 'hex')
const b = Buffer.from(parts.v1 ?? '', 'hex')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}Retries
Any non-2xx response, timeout or connection error is retried with backoff:
| Attempt | Delay after previous failure |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 12 hours |