Appearance
Webhooks
If you pass callbackUrl to createTask, we POST the final task state to that URL when the task finalizes. The URL is stored on the task hash at admission and expires with the result TTL.
Payload
http
POST https://yourapp.example.com/captchaai/webhook
Content-Type: application/json
X-CaptchaAI-Signature: t=1715865600,v1=…hex…
X-CaptchaAI-Event: task.readyjson
{
"taskId": "tsk_…",
"status": "ready",
"solution": { "text": "…" },
"createdAt": "2026-05-16T08:40:00Z",
"finalizedAt": "2026-05-16T08:40:12Z"
}Signature verification
js
const t = Date.now() / 1000
const body = await req.text()
const expected = hmacSha256Hex(secret, `${t}.${body}`)
// Compare in constant time against the v1 value in X-CaptchaAI-Signature.Reject if |now - t| > 300 s to prevent replay.
Retry schedule
| Attempt | Delay |
|---|---|
| 1 | immediate |
| 2 | 5 s |
| 3 | 30 s |
| 4 | 5 min |
| 5 | 30 min |
| 6 | 2 h |
After attempt 6 the delivery is parked in callbacks.dlq. The admin Callback inspector page can replay it.
Idempotency on your side
We may deliver the same event more than once (network races, retries after a 5xx that did succeed). Treat taskId + status as the dedup key.