The Adjuro Receipt Protocol
How a receipt is structured, signed, and verified — so any third party can confirm who an AI agent was, what consent authorized the call, and on whose behalf, offline, in under 50 milliseconds.
LAST UPDATED May 22, 2026
01Overview
An Adjuro receipt is a JOSE-standard JWS (JSON Web Signature) issued the moment an AI voice agent places a call on behalf of a brand. It is a sworn attestation — adjurō, "I attest under oath" — that binds five facts together under a single Ed25519 signature: who the agent is, what consent authorized the call, on whose behalf it was placed, under what scope and jurisdiction, and at what instant.
A receipt is evidence of the tenant's attestation; it is not, by itself, proof that the recipient consented. Its value is that the attestation is cryptographically non-repudiable and independently verifiable: a TCPA defense attorney, a compliance dashboard, or opposing counsel can verify it against Adjuro's published keys without trusting — or even contacting — Adjuro at verification time.
What a verifier learns
The signature proves the receipt payload was attested byte-for-byte by the holder of a specific Adjuro signing key at issuance time, and has not been altered since. Everything a relying party needs to make that determination is in the receipt and the public JWKS — no Adjuro account, no API call to us, no network dependency on our uptime.
02Receipt payload schema
Receipts use dual naming: each JOSE-standard claim (RFC 7519) is paired with a human-readable alias carrying the identical value. Both are populated on every receipt. Verifier code may use either set; non-developer compliance reviewers and FRE 901 evidence packets benefit from the human-readable form.
| Purpose | JOSE-standard | Human-readable alias | Type / Format |
|---|---|---|---|
| Issuer URL | iss | issued_by | string (URL), same value in both |
| Receipt opaque ID | jti | receipt_id | string (adj_rcpt_<base32>), same value |
| Issuance instant | iat | issued_at | iat = unix seconds (int); issued_at = RFC 3339 (string). Same instant. |
| Expiry instant | exp | expires_at | exp = unix seconds (int); expires_at = RFC 3339 (string). Same instant. |
| Replay nonce | nonce | replay_token | string (16+ chars opaque), same value |
Adjuro-specific claims
These are not JOSE-standard and are single-named:
| Field | Type | Description |
|---|---|---|
tenant_id | uuid string | The Adjuro tenant that authorized this receipt |
trust_root_id | string | The federation trust root that signed (e.g., adjuro-root-2026w20) |
agent_id | string | adj:<base32-blake3> — deterministic from tenant + agent name |
brand | string | Display name of the brand the call is made on behalf of |
event_type | enum | V0: voice_call. Reserved: api_request, chat_message, email_send, payment_auth, workflow_action |
caller_number | E.164 string | Caller side, plaintext (no PII concern on the caller side) |
callee_hash | base64url string | HMAC-SHA256 of callee E.164 under the per-tenant salt — recipient's plaintext number is never received or stored |
campaign_id | string | Customer-supplied opaque reference (indexed for audit queries) |
consent_id | string | Customer-supplied opaque CRM reference — Adjuro stores but never resolves |
scope | string[] | V0 enum: account_servicing, appointment_reminder, debt_collection, marketing, support, verification |
jurisdiction | string | ISO 3166-2 (e.g., US-CA, GB, DE-BY) |
source_url | string | null | Optional customer-controlled URL pointing to the consent artifact |
03JWS protected header
Every receipt carries this protected header:
alg: EdDSA— pure Ed25519 per RFC 8037. Verifiers MUST reject any receipt whosealgis not exactlyEdDSA(algorithm-confusion defense).kid— the signing-key id from Adjuro'ssigning_keyshistory at issuance time. Used to resolve the public key in the JWKS. Keys rotate every 14 days with a 48-hour overlap.typ: JWT— for out-of-the-box compatibility with standard JOSE libraries on the verify path.
Load-bearing: pure Ed25519, not pre-hash (D15)
Adjuro signs with ED25519_SHA_512 (pure Ed25519, RFC 8032 §5.1), never ED25519_PH_SHA_512 (pre-hash). JOSE RFC 8037 defines alg: EdDSA as pure mode, and there is no registered JWS identifier for pre-hash Ed25519 — so pre-hash would break every standard JOSE library (jose, python-jose, go-jose). The HSM signs the actual receipt bytes, not an application-computed hash, which also yields a cleaner FRE 901 evidentiary statement: "AWS KMS attested to this specific receipt payload byte-for-byte."
04Verification algorithm
Verification is fully offline against the published JWKS. The only network fetch — the JWKS itself — is cacheable for 5 minutes at the CDN edge and can be pinned for air-gapped review. Steps 7 and 8 are optional hardening.
The open-source verifier SDK (adjuro on npm, Apache 2.0, TypeScript/Node) implements exactly this algorithm. Because alg: EdDSA is a JOSE standard, a hand-rolled verifier using any compliant JOSE library also works without an Adjuro dependency.
05Trust model
Adjuro is designed as a federated trust network, not a single issuer. The long-term shape is "Let's Encrypt for AI agents": multiple operators run their own signing roots, and Adjuro runs the verifier registry that lets any relying party resolve and trust them.
Trust roots
A receipt's trust_root_id names the signing root that attested it. Each root publishes its own keys; a verifier decides which roots it accepts. Three roots ship in V0:
adjuro-root-2026w20— active. The production Adjuro signing root, rotated every 14 days (the2026w20suffix is ISO year + week number).vapi-root-reserved— reserved placeholder for the Vapi platform to run its own root.retell-root-reserved— reserved placeholder for the Retell platform.
When a voice-agent platform operates its own signing root, every receipt it issues verifies through the same registry with no retrofit on the verifier side. A separate adjuro-degraded-2026w20 key signs degraded-mode entries (issued when signing is briefly unavailable and the call proceeds unsigned) — kept distinct from the production root so degraded evidence is never confused with a clean attestation.
Why federation is the structural defense
Because Adjuro runs the verifier registry rather than being the only issuer, it stays neutral across trust boundaries — an incumbent bundling identity into its own platform cannot replicate cross-operator neutrality without becoming a neutral registry itself. Verification is winner-take-most once a transparency log plus federation reach critical mass.
06Schema stability commitment
Frozen at V0 ship. Every field name in §02 — the JOSE-standard set (iss, jti, iat, exp, nonce), the human-readable alias set (issued_by, receipt_id, issued_at, expires_at, replay_token), and every Adjuro-specific field — is a public contract. Changing any field name after V0 ships breaks every deployed verifier in the wild.
Allowed without a breaking change
- Adding new fields to the payload (verifiers ignore unknown fields per JOSE convention).
- Adding new enum values to
scopeorevent_type(existing values keep working). - Adding new top-level Adjuro-specific claims.
Requires a breaking-version bump + multi-year deprecation window
- Renaming any existing field.
- Removing any existing field.
- Changing a field's type or format — the dual-naming convention exists specifically so neither representation of a timestamp ever needs to change.
- Removing enum values from
scopeorevent_type.
This commitment is enforced in CI by the integration test "receipt payload contains both JOSE-standard and human-readable claim names". If anyone ever drops a field, that test fails immediately.