Skip to content

Receipt and event formats ​

Every signed Axiom document is a JWS (RFC 7515) with alg ES256 or EdDSA, a kid and a typ. Payloads are canonicalised with the JSON Canonicalization Scheme (RFC 8785) before signing or hashing. Every digest field has the form sha-256: followed by the base64url (unpadded) SHA-256.

FormattypSigned by (key role)Where it appears
axiom.action.v1— (not signed; hashed)—request_digest, approvals, dispatch claims
axiom.dispatch.v1axiom-dispatch+jwsAxiom grant keyAxiom-Authorization header to the upstream
axiom.outcome.v1axiom-outcome+jwsProvider's outcome keyaxiom-outcome response header, POST /v1/actions/:id/outcome, receipt
axiom.receipt.v1axiom-receipt+jwsAxiom observation keyReceipt bundle
axiom.receipt.bundle.v1— (container)—GET /v1/actions/:id/receipt, exports
axiom.event.v1axiom-event+jwsAxiom event keyWebhook and relay deliveries
axiom.observation.v1axiom-observation+jwsAxiom observation keyStored with each approval decision

Axiom's public keys, current and historical, are published at https://api.sectigoaxiom.com/.well-known/jwks.json with axiom:role (identity, grant, observation, policy, event) and axiom:status (active, retired, revoked). Retired keys stay published so older documents keep verifying.

axiom.action.v1 ​

The canonical action: the versioned representation of exactly what the person approved and what the gateway authorised (packages/core/src/canonical.ts). request_digest is sha-256(JCS(canonical action)). Any change to a field changes the digest, which invalidates an existing approval.

FieldTypeMeaning
v"axiom.action.v1"Version
tenantstringTenant ID, taken from the authenticated agent, never from the request body
agentstringStable agent ID
audiencestringProtected service ID
operationstringOperation name, e.g. billing.refund.create
resource{ type, id }The business resource, e.g. { "type": "invoice", "id": "417" }
paramsobjectMaterial fields only, normalised: decimals as canonical strings ("25000.00" becomes "25000"), currencies upper-case, integers as numbers
not_afterRFC 3339End of the request's validity (default 24 hours after submit; reused on replay)
root_actionstring, optionalRoot action ID for a delegated child operation
json
{
  "v": "axiom.action.v1",
  "tenant": "ten_01kd0m4z9x8c7v6b5n4m3l2k1j",
  "agent": "agt_01kd0m6a1b2c3d4e5f6g7h8j9k",
  "audience": "svc_01kd0m5q1w2e3r4t5y6u7i8o9p",
  "operation": "billing.refund.create",
  "resource": { "type": "invoice", "id": "417" },
  "params": { "amount": "25000", "beneficiary": "acct_acme_ops", "currency": "USD" },
  "not_after": "2026-11-15T18:00:00.000Z"
}

axiom.dispatch.v1 ​

Forwarded by the gateway to the upstream in the Axiom-Authorization header (and returned to an embedded-SDK provider as claim.authorization from POST /v1/authorize). It states exactly what was authorised, so the application can verify trusted context at its boundary. Lifetime: 120 seconds. The same token authenticates Axiom's reconciliation lookup.

ClaimTypeMeaning
v"axiom.dispatch.v1"Version
issstringAxiom issuer, e.g. https://api.sectigoaxiom.com
audstringOrigin of the upstream the request is addressed to
action_id, root_action_idstringAction and root action
operation_keystringOperation key; also sent as Idempotency-Key
operation, tenant, agentstringWhat, for whom, by whom
resource{ type, id }Authorised resource
request_digeststringDigest of canonical
canonicalaxiom.action.v1The approved canonical request; recompute its digest and compare its params with the body received
claim_idstringThe single execution claim
iat, expnumberIssue and expiry (seconds)

Headers sent with it: Idempotency-Key, Axiom-Action-Id, Axiom-Root-Action-Id. The SDK checks all of this in AxiomProvider.verifyDispatch (Embedded SDK provider guide).

axiom.outcome.v1 ​

The provider's attestation of what its system of record committed. Only a valid attestation, signed by an outcome key registered on the service (outcome_keys) and bound to the action, makes a receipt "destination confirmed".

FieldTypeMeaning
v"axiom.outcome.v1"Version
action_idstringMust equal the action
operation_keystringMust equal the action's operation key
request_digeststringMust equal the action's request digest
statesucceeded | failedCommitted result. failed means the business rules rejected it and nothing was written
target_refstring, optionalProvider's record ID, e.g. the refund ID
recorded_atRFC 3339When the provider recorded it
detailobject, optionalProvider-chosen result fields, returned to the agent as outcome.result

axiom.receipt.v1 ​

The receipt claims (ReceiptClaims in packages/core/src/receipts.ts), signed as a JWS JSON General Serialization whose payload is the JCS of the claims. Each signature's protected header carries typ: axiom-receipt+jws, role: observation and kid. A receipt is issued when an action reaches an outcome (succeeded, failed or outcome_unknown) and reissued, with the same ID and salts, if reconciliation later establishes the final state.

FieldTypeMeaning
v"axiom.receipt.v1"Version
receipt_idstringrcp_…
issuer, issued_atstringSigner and time of this issue
action_id, root_action_id, parent_action_id?stringAction and delegation chain
operation_keystringOperation key
tenant, project, audience, operationstringContext
resource{ type, id }Resource
agentobjectid, credential: { kind, thumbprint, issuer? }, operator?, publisher?, sponsor?
request_digeststringDigest of the canonical action
commitmentsobjectPer canonical field, sha-256(salt + "." + JCS(value)). Field names: tenant, agent, audience, operation, resource, not_after, root_action, and params.<name>
grantobjectid, parent?, issued_by, expires_at
policyobjectversions: { id, version, layer }[], decision: allow | require_approval, reason_code
approvalsarrayPer decision: approval_id, approver: { id, display? }, role, decision, decided_at, digest, source: { system, native_ref?, verified_via: axiom_session | connector_readback | signed_callback }
dispatchobject, optionalat, claim_id, via: proxy | sdk | adapter, gateway
outcomeobject, optionalstate: succeeded | failed | outcome_unknown, recorded_at, target_ref?, attestation? (the provider's compact axiom.outcome.v1), observed?: { http_status?, observed_at } when only the gateway saw a response
phasesobjectauthorized, approved (allowed within the grant, or every required approval approved), dispatched, destination_confirmed (booleans as recorded by Axiom; a verifier recomputes dispatched and destination_confirmed)

When an external approval system cannot sign the human's decision, Axiom signs its own verified observation (verified_via: connector_readback) and keeps the native reference, so the receipt names the actual signer.

axiom.receipt.bundle.v1 ​

What GET /v1/actions/:id/receipt (and the SDK, CLI and exports) return. It verifies offline.

FieldTypeMeaning
type"axiom.receipt.bundle.v1"Container type
receiptGeneral JWS{ payload, signatures: [{ protected, signature }] } over axiom.receipt.v1
disclosuresobjectField name → { salt, value }. A redacted export (?reveal=operation,params.amount) contains only the named fields; the commitments still prove what is shown
keysJWK[]Snapshot of Axiom's public keys (and, for older verifiers, the provider keys) at export time
provider_keysJWK[]The service's registered provider outcome keys at export time. Verifiers take outcome keys from here
statusobject, optional{ checked_at, keys: { kid: active | retired | revoked }, source }: key status at export

Pin keys for trust

The embedded keys let anyone check a bundle's internal consistency without network access, but a bundle could carry any keys. For a trust decision, verify with Axiom's published JWKS and the provider's published outcome keys (axiom verify --issuer-jwks … --provider-jwks …), or pinned keys.

How the reference verifier (verifyReceiptBundle in packages/core/src/receipts.ts) keeps the two signer roles apart:

  • The receipt signatures are checked against the pinned issuer keys, or the keys snapshot.
  • Outcome attestations are checked against the pinned provider keys, else provider_keys, else keys, after removing every key ID that signed the receipt and every pinned issuer key. An Axiom key therefore can never pass as the provider's outcome signer.
  • The attestation's protected header must have typ: axiom-outcome+jws, and its action_id, request_digest and operation_key must equal the receipt's.

axiom.event.v1 ​

The data-minimised wake event delivered to webhooks and relays, as a compact JWS (content-type: application/jose, header Axiom-Event-Id). It is a hint to read status: it carries no state an agent may act on and cannot grant authority.

FieldTypeMeaning
v"axiom.event.v1"Version
event_idstringUnique per delivery target and change; receivers deduplicate on it
typeaction.changed (also defined: agent.suspended, grant.revoked, policy.updated)Event type. The current release delivers action.changed
action_idstring, optionalAction that changed
tenantstringTenant
occurred_atRFC 3339When the change was recorded
seqnumberPer-action sequence at the time of the change; drop events with a seq at or below the last processed

Other signed documents ​

  • Policy versions are signed on publish with the policy key (typ: axiom-policy+jws) over { policy_id, version, digest, scope, signed_at }.
  • Gateway bundles (GET /v1/gateways/:gid/bundle, heartbeat responses) are compact JWS from the policy key with issued_at, fresh_until (300 seconds), policy_version, executor, policies[], trust and emergency_denies[].
  • Incident bundles are signed with the event key and omit request fields.
  • SIEM events are unsigned JSON over an authenticated channel: { id, seq, occurred_at, org_id, tenant_id, project_id, type, action_id, actor: { type, id }, policy_version, evidence_ref, outcome, data }. See SIEM delivery.

Developer preview. Contract version 2026-11-14.