Webhooks and relay
A wake event tells a sleeping workload that one of its actions changed. It carries no state you may act on: the consumer verifies it, then reads authenticated status. A relay that delivers events cannot grant authority or execute anything, and duplicate, delayed or out-of-order deliveries converge on the same record.
Register a webhook
Webhooks belong to a Gateway project. A project member with project.write (project owner or developer) registers one with a console session, or from the console's Webhooks page (/app/projects/{project_id}/webhooks).
POST /v1/projects/{project_id}/webhooks
| Field | Type | Notes |
|---|---|---|
url | URL | Must be https:// except in a development deployment. |
events | string[] | Default ["action.changed"]. "*" also matches. Only action.changed is sent today. |
agent_id | string, optional | Only events for actions of this agent. |
tenant_id | string, optional | Only events for actions in this tenant. |
{ "url": "https://agent.example.com/axiom/wake", "events": ["action.changed"], "agent_id": "agt_01kd0m6n3b4v5c6x7z8a9s0d1f" }{
"id": "whk_01kd0mb2…",
"url": "https://agent.example.com/axiom/wake",
"events": ["action.changed"],
"agent_id": "agt_01kd0m6n3b4v5c6x7z8a9s0d1f",
"verification_keys": "https://api.sectigoaxiom.com/.well-known/jwks.json"
}GET /v1/projects/{project_id}/webhooks lists them. There is no update or delete endpoint in this preview.
Who registers the webhook
The project belongs to the provider, so the provider's developer registers webhooks, typically one per customer agent (agent_id) or per customer tenant (tenant_id). An unfiltered webhook receives events for every action in the project. Events carry only IDs, so a shared endpoint learns little, but filter anyway.
When events are sent
Every state transition of an action (transition() in services/actions.ts) queues one event per matching webhook: approval, denial, expiry, cancellation, revocation, the execution claim, outcome_unknown and the final outcome. Creating the action does not send an event (the submitter already has the response), and neither do approval delivery or individual decisions that do not change the state (for example the first of two required approvals).
The delivery
POST /axiom/wake HTTP/1.1
Host: agent.example.com
Content-Type: application/jose
Axiom-Event-Id: evt_01kd0mc4…:act_01kd0m7s2v8x9yz3abcdefghjk:7
eyJ0eXAiOiJheGlvbS1ldmVudCtqd3MiLCJhbGciOiJFUzI1NiIsImtpZCI6Ii4uLiJ9.eyJ2IjoiYXhpb20uZXZlbnQudjEiLC4uLn0.…The body is a compact JWS with header typ: axiom-event+jws, signed by Axiom's key with role event (published in the issuer JWKS with "axiom:role": "event"). The payload is an axiom.event.v1 wake event:
{
"v": "axiom.event.v1",
"event_id": "evt_01kd0mc4…:act_01kd0m7s2v8x9yz3abcdefghjk:7",
"type": "action.changed",
"action_id": "act_01kd0m7s2v8x9yz3abcdefghjk",
"tenant": "ten_01kd0m4z9x8c7v6b5n4m3l2k1j",
"occurred_at": "2026-11-14T18:12:03.000Z",
"seq": 7
}seqis the action's event sequence at the time of the change. It is stamped when the change happens and carried unchanged by every retry, so a later change always has a higherseqand a delayed delivery is recognisably older.occurred_atis when the change was queued for delivery, not when the delivery was sent.- Answer any
2xxto acknowledge. Anything else, or no answer within 10 seconds, is retried with exponential backoff (2^attempt × 5seconds, at most 3600 s between attempts) up to 10 attempts, after which the delivery is marked dead.
Field definitions are in receipt and event formats.
Consume it
- Verify the signature against the issuer JWKS, using only keys with role
eventthat are not revoked; checktyp, the age and thattenantis yours. Respond400and ignore anything else. - Note whether you already processed this
(action_id, seq)or a higherseqfor that action (a duplicate or stale delivery), but do not act on the event's position alone. - Read authenticated status as the agent, for fresh, duplicate and stale events alike. The read returns the record's current state, never the state the event describes, so an out-of-order delivery cannot move you backwards.
- Mark the
seqprocessed only after the status read succeeded, so a failed read leaves the redelivery classified as fresh. - Act on the status: resume if
approved, report and stop if closed, keep tracking if in flight.
With the SDK:
import { AxiomAgent, WakeEventReceiver, handleWake } from "@sectigo/axiom-sdk";
const receiver = new WakeEventReceiver({
issuer: "https://api.sectigoaxiom.com", // keys from {issuer}/.well-known/jwks.json, role "event"
tenant: "ten_01kd0m4z9x8c7v6b5n4m3l2k1j", // reject events for other tenants
// seqStore: a durable SeqStore; the default MemorySeqStore forgets on restart
});
export async function onWake(req: Request): Promise<Response> {
let wake;
try {
wake = await handleWake(agent, receiver, await req.text());
} catch {
return new Response(null, { status: 400 }); // bad signature, type, age or tenant
}
if (wake.status?.state === "approved") {
const record = await persistence.get(wake.status.action_id);
if (record) await agent.continue(record);
}
return new Response(null, { status: 204 });
}receiver.receive(body) returns { event, disposition } with fresh, duplicate or stale. handleWake reads authoritative status for every event that names an action, whatever its disposition (a duplicate is how a relay retries after the first read was lost), then calls markProcessed, which keeps the highest seq. Use disposition if you want to skip work you have already done. Back the SeqStore with durable storage if your consumer restarts.
The receiver rejects events older than maxAgeSeconds, default 86400 (24 hours), which covers the full retry schedule. Replays inside that window are harmless: deduplication by (action_id, seq) and the mandatory status read are the defence, and an event never carries authority.
Relays and privacy
Events are data-minimised by design: IDs, a timestamp and a sequence number, no summary, amounts or parameters. A relay that forwards them (for example into a customer's message bus) learns which actions changed and when, nothing else, and cannot forge events without Axiom's event key. End-to-end encryption of events for relays that must not see even the IDs is not implemented in this preview. Relay configuration for administrators is in relay and status freshness.