Webhooks
Webhooks are how paused agents wake up. Register an endpoint in Dashboard → Webhooks and every state change in your workspace is POSTed to it, signed, within moments of happening.
Events
| Event | Fires when |
|---|---|
| request.created | A request was persisted and the approver notified. |
| request.approved | A human approved — possibly with an edited payload. |
| request.rejected | A human rejected. The note, if any, is included. |
| request.expired | The TTL elapsed with no decision. |
Delivery payload
POST <your endpoint>
{
"event": "request.approved",
"createdAt": "2026-07-15T20:05:00.000Z",
"data": {
"id": "clx…",
"status": "APPROVED",
"summary": "Refund $10,000 to customer #4821",
"payload": { "action": "refund", "amount": 10000 },
"modifiedPayload": { "action": "refund", "amount": 10.00 },
"effectivePayload": { "action": "refund", "amount": 10.00 },
"metadata": { "runId": "run_442" },
"agentName": "SupportBot",
"approverEmail": "finance@company.com",
"resolvedAt": "2026-07-15T20:05:00.000Z",
"resolvedByEmail": "finance@company.com",
"resolutionNote": "Fixed the amount.",
"expiresAt": "2026-07-16T20:00:00.000Z",
"createdAtRequest": "2026-07-15T20:00:00.000Z"
}
}Deliveries also carry three headers:
| Header | Contents |
|---|---|
| X-Confirm-Event | The event name, duplicated for cheap routing. |
| X-Confirm-Delivery-Id | Stable per delivery — use it as an idempotency key. |
| X-Confirm-Signature | t=<unix_ts>,v1=<hex> — the HMAC described below. |
Verifying signatures
Each endpoint has a whsec_… secret, shown once at creation. The signature is HMAC-SHA256 over "<timestamp>.<raw body>". Verify with a timing-safe comparison and reject stale timestamps to block replays:
verify.ts
import { createHmac, timingSafeEqual } from "crypto";
export function verifyConfirmSignature(
secret: string,
header: string, // X-Confirm-Signature
rawBody: string, // the unparsed request body
toleranceSec = 300,
) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSec) return false;
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}GOTCHA
Sign-check the raw body string, before any JSON parsing. Frameworks that re-serialize the body will change whitespace and break the HMAC.
Retries & reliability
- Any 2xx from your endpoint counts as delivered.
- Non-2xx responses and timeouts (10s) are retried up to 3 attempts with backoff.
- Every attempt is recorded — endpoint, event, HTTP status, error — and visible on the request's detail page in the dashboard.
- Design your handler to be idempotent using
X-Confirm-Delivery-Id; retries mean you can occasionally see the same delivery twice.
Webhooks are the recommended integration, but they're optional — polling GET /v1/requests/:id gives identical data with more latency.