Webhooks
A22A posts run.completed and run.failed to your endpoint, signed with its secret.
Setting an endpoint
Set the endpoint on Webhook in the A22A dashboard. It must be an https URL on a public host. Setting it creates a new signing secret, shown once; the previous secret stops working. With no endpoint set, no events are sent; retrieve the run instead.
Events
When a run ends, A22A posts one event to the endpoint. type is run.completed or run.failed; data is the run, in the same shape as retrieving it.
{
"type": "run.completed",
"created": "2026-10-28T09:41:12.000Z",
"data": {
"run_id": "4b3e…",
"status": "completed",
"steps": [
{ "step_id": "extract", "status": "completed", "output": "…" },
{ "step_id": "write", "status": "completed", "output": "…" }
]
}
}Webhook signing
Every event is signed with the endpoint's secret. Verify the signature and parse the event with constructEvent. Pass the raw request body; the signature covers the exact bytes sent.
export async function POST(req: Request) {
const event = a22a.webhooks.constructEvent({
body: await req.text(),
signature: req.headers.get("a22a-signature"),
secret: "whsec_…"
});
// { type: "run.completed", created: "…", data: { run_id, status, steps } }
if (event.type === "run.completed") await save(event.data);
return new Response(null, { status: 200 });
}A missing, malformed, stale or mismatched signature throws InvalidWebhookSignatureError.
The signature is in the a22a-signature header:
a22a-signature: t=1793180472,v1=4f1c…t is the Unix time it was signed. v1 is the hex HMAC-SHA256 of <t>.<body>, keyed with the endpoint's secret. constructEvent rejects a signature more than 5 minutes old.
Responding
Respond with a 2xx once you have handled the event; anything else is retried. No response within 10 seconds counts as a failure. An event gets up to 6 attempts, with a longer wait before each one. The same event can arrive more than once; handle each run_id once.