Skip to content

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

FieldTypeNotes
urlURLMust be https:// except in a development deployment.
eventsstring[]Default ["action.changed"]. "*" also matches. Only action.changed is sent today.
agent_idstring, optionalOnly events for actions of this agent.
tenant_idstring, optionalOnly events for actions in this tenant.
json
{ "url": "https://agent.example.com/axiom/wake", "events": ["action.changed"], "agent_id": "agt_01kd0m6n3b4v5c6x7z8a9s0d1f" }
json
{
  "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 ​

http
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:

json
{
  "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
}
  • seq is 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 higher seq and a delayed delivery is recognisably older.
  • occurred_at is when the change was queued for delivery, not when the delivery was sent.
  • Answer any 2xx to acknowledge. Anything else, or no answer within 10 seconds, is retried with exponential backoff (2^attempt × 5 seconds, 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 ​

  1. Verify the signature against the issuer JWKS, using only keys with role event that are not revoked; check typ, the age and that tenant is yours. Respond 400 and ignore anything else.
  2. Note whether you already processed this (action_id, seq) or a higher seq for that action (a duplicate or stale delivery), but do not act on the event's position alone.
  3. 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.
  4. Mark the seq processed only after the status read succeeded, so a failed read leaves the redelivery classified as fresh.
  5. Act on the status: resume if approved, report and stop if closed, keep tracking if in flight.

With the SDK:

ts
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.

Developer preview. Contract version 2026-11-14.