ADJURO
PROTOCOL SPECIFICATION
CRYPTOGRAPHIC EVIDENCE FOR AI AGENTS
2026—

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.

VERSION V0
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.

PurposeJOSE-standardHuman-readable aliasType / Format
Issuer URLississued_bystring (URL), same value in both
Receipt opaque IDjtireceipt_idstring (adj_rcpt_<base32>), same value
Issuance instantiatissued_atiat = unix seconds (int); issued_at = RFC 3339 (string). Same instant.
Expiry instantexpexpires_atexp = unix seconds (int); expires_at = RFC 3339 (string). Same instant.
Replay noncenoncereplay_tokenstring (16+ chars opaque), same value

Adjuro-specific claims

These are not JOSE-standard and are single-named:

FieldTypeDescription
tenant_iduuid stringThe Adjuro tenant that authorized this receipt
trust_root_idstringThe federation trust root that signed (e.g., adjuro-root-2026w20)
agent_idstringadj:<base32-blake3> — deterministic from tenant + agent name
brandstringDisplay name of the brand the call is made on behalf of
event_typeenumV0: voice_call. Reserved: api_request, chat_message, email_send, payment_auth, workflow_action
caller_numberE.164 stringCaller side, plaintext (no PII concern on the caller side)
callee_hashbase64url stringHMAC-SHA256 of callee E.164 under the per-tenant salt — recipient's plaintext number is never received or stored
campaign_idstringCustomer-supplied opaque reference (indexed for audit queries)
consent_idstringCustomer-supplied opaque CRM reference — Adjuro stores but never resolves
scopestring[]V0 enum: account_servicing, appointment_reminder, debt_collection, marketing, support, verification
jurisdictionstringISO 3166-2 (e.g., US-CA, GB, DE-BY)
source_urlstring | nullOptional customer-controlled URL pointing to the consent artifact

03JWS protected header

Every receipt carries this protected header:

{ "alg": "EdDSA", "kid": "adjuro-root-2026w20", "typ": "JWT" }

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.

# Verify an Adjuro receipt (compact JWS: header.payload.signature) function verifyReceipt(jws, options): header = base64urlDecodeJSON(segment(jws, 0)) payload = base64urlDecodeJSON(segment(jws, 1)) # 1. Reject anything that is not pure Ed25519 (alg-confusion defense) assert header.alg == "EdDSA" # 2. Resolve the signing key by kid against the published JWKS jwks = httpGet("https://api.adjuro.ai/.well-known/jwks.json") # 5-min CDN cache key = jwks.keys.find(k => k.kid == header.kid) assert key != null # unknown kid -> reject # 3. Verify the Ed25519 signature over the EXACT signed bytes. # Pure Ed25519 / RFC 8032 — the verifier does NOT pre-hash. signedInput = segment(jws, 0) + "." + segment(jws, 1) assert ed25519Verify(key, signedInput, signature(jws)) # 4. Temporal validity (receipts are short-lived) now = unixSeconds() assert payload.iat <= now assert now < payload.exp # 5. Trust root must be one the relying party accepts assert payload.trust_root_id in options.acceptedTrustRoots # 6. (optional) Confirm the receipt has not been revoked if options.checkRevocation: rev = httpGet("https://api.adjuro.ai/v1/revocations") # SDK polls /15min assert payload.jti not in rev.revoked_receipt_ids # 7. (optional) Confirm public transparency-log inclusion if options.checkTransparencyLog: proof = httpGet("https://log.adjuro.ai/proof/" + payload.jti) assert verifyInclusionProof(proof, payload.jti) return VERIFIED(payload)

The open-source verifier SDK (github.com/adjuro/sdk, Apache 2.0, single-file Node + Python + Go) 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:

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

Requires a breaking-version bump + multi-year deprecation window

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.