This page describes that document: what is in it, why it is shaped the way it is, and how you verify one on your own machine.
The question a receipt answers#
A Decision Receipt is designed to let any party later prove one precise thing: at time T, agent A acting for company B presented grant C to relying party D for action E; Delegus evaluated it against this identity state, this revocation state, these rules and this trust configuration, and decided X.
It has to keep proving that after keys rotate, after the company changes its DID document, and after the revocation list has been re-signed a thousand times. So the receipt does not link to any of those things. It pins their hashes, and Delegus keeps the bytes.
What is inside#
Here is the shape v0.2 specifies (delegus-), trimmed:
{
"receipt_version": "delegus-receipt-v1",
"receipt_id": "drc_01K4Q7ZP9X2M3N4R5S6T7V8W9Y",
"decision": "ALLOW",
"reason": null,
"evaluated_at": "2026-09-09T14:03:27.412Z",
"relying_party": "did:web:delegus.ai:rp:seller",
"principal": { "id": "did:web:acme.com", "verification": "domain" },
"agent": "did:key:z6Mk...",
"grant_id": "urn:delegus:grant:01J9X4...",
"grant_hash": "sha256:...",
"proof_hash": "sha256:...",
"request_hash": "sha256:...",
"authority": { "action": "commerce:purchase",
"constraints": { "maxAmount": 50000, "currency": "USD" } },
"expires_at": "2026-10-01T00:00:00Z",
"evidence": {
"issuer_resolution": { "did": "did:web:acme.com",
"did_document_hash": "sha256:...",
"resolved_at": "2026-09-09T14:00:11Z" },
"status": { "credential_url": "https://status.delegus.ai/acme.com/1",
"credential_hash": "sha256:...",
"checked_at": "2026-09-09T14:03:27.410Z",
"status_list_index": "4127" },
"trust_config_version": "trust_0042",
"trust_config_hash": "sha256:..."
},
"protocol": { "result": "PASS", "profile": "delegus-base-v1",
"profile_hash": "sha256:...",
"checks": { "P1": true, "P2": true, "...": "...", "P20": true } },
"trust": { "result": "PASS", "policy_version": "delegus-trust-v1",
"checks": { "T1": true, "T2": true, "T3": true, "T5": true } },
"receipt": "<compact JWS>"
}Read it as five layers.
1. The decision. decision is ALLOW or DENY. reason is null on ALLOW; on DENY it is the first check that failed, in the fixed evaluation order — a stable string like AMOUNT_, AUTHORITY_ or PROOF_REPLAYED that your code can switch on. There is one reason, never a list, because the order is part of the contract.
2. The parties and the authority. Who the relying party was, which company issued the grant and how that company was verified ("domain"), the agent's key, the grant id, and the specific capability the action matched, with its constraints. One rule worth knowing: on a DENY where the grant's signature did not check out, these attribution fields are omitted. Delegus never echoes an attribution it could not authenticate.
3. What was presented. grant_hash, proof_hash and request_hash are SHA-256 over the exact bytes Delegus saw — the compact JWS strings as received, and the canonicalized action. Delegus does not store your grants, proofs or actions; you keep those. The receipt makes it impossible to substitute different ones later.
4. What the world looked like. This is the evidence block, and it is the part that makes offline verification possible. The company's DID document as fetched, the revocation status-list credential as evaluated, and the trust-configuration descriptor in force — each pinned by hash. Not "the current DID document". The one that was there at evaluated_at.
5. What rules ran, and how it went. protocol.checks lists every check P1 through P20 with true, false or "skipped", and profile_hash is the hash of the published profile document that defines them. trust.checks is the same for T1, T2, T3 and T5, Delegus's own trust checks (is this issuer known, verified, not flagged; has this proof been seen before).
Then receipt: a non-detached compact JWS over the JCS-canonicalized body, alg: EdDSA, typ: delegus-, kid: did:web:delegus.. The JWS string is the canonical receipt. The JSON is a convenience and must equal the payload.
One hash encoding, and why it is boring on purpose#
Every hash in the receipt is sha256:<64 lowercase hex> over defined bytes. No multihash, no base64url, no "canonical JSON except when". The evidence store's object keys are the hashes themselves, so GET / returns bytes whose hash is, by construction, the key you asked for. A key can never disagree with its content, so there is nothing to argue about.
Re-verifying a receipt without Delegus#
Here is what v0.2 specifies. Given:
- the receipt,
- Delegus's receipt public key (published in the DID document at
delegus., where every key that has ever signed a receipt within its retention period stays published — rotation addsai/ . well- known/ did. json receipt-, it never removes<n+1> receipt-),<n> - and the four evidence artifacts named by
did_,document_ hash credential_hash,profile_hashandtrust_, fetched by hash,config_ hash
a third party can re-run delegus-base-v1 and confirm protocol.result.
Two details make this work.
The clock is in the receipt. Time-dependent checks — is the grant inside its validity window, is the proof fresh — must be evaluated with now set to the receipt's own signed evaluated_at, never the verifier's wall clock. v0.2 makes this normative. Because evaluated_at is inside the signed payload, nobody can move the evaluation time without breaking the signature. So a receipt for a grant that expired in 2026 still reproduces ALLOW in 2031, correctly, because the question is whether it was valid at the time.
The trust block is not reproducible, by design. protocol.checks are the open, published rules; anyone can re-run them. trust.checks are Delegus's attestation — that the issuer was registered and domain-verified, that the proof's jti had never been consumed before. You cannot recompute those from public inputs, and the receipt does not pretend you can. What you can verify is that Delegus signed that attestation, at that time, over those exact inputs.
That split is the honest line between an open format and a run service: anyone can verify a Delegus artifact; only Delegus can produce a fresh one.
Doing it#
The SDK exposes it as one call, and the CLI wraps the same code. No API key is needed — this is the point.
delegus receipt verify \
--receipt receipt.json \
--grant grant.jws \
--proof proof.jws \
--action action.jsonIt checks the signature against the published keys, recomputes every pinned hash from the bytes you supplied, fetches the evidence artifacts by hash, re-runs the protocol checks with the receipt's clock, and reports three things: whether the signature verifies, whether each hash matches, and the reproduced protocol result. Exit code 0 if everything holds, 2 if anything does not. In code:
const r = await delegus.receipts.reverify({ receipt, grant, proof, action });
// r.ok, r.signature, r.hashes, r.reproducedWhy DENY receipts matter as much as ALLOW receipts#
It is tempting to think of the receipt as a proof of permission. It is more useful as a proof of decision. When a company later says "our agent never had authority to do that", a DENY receipt with AMOUNT_ and the hash of the exact request is the seller's answer. When a seller says "we were never told the grant was revoked", a receipt pinning the status-list credential as it stood — and the spec's rule that a successful /revoke is seen by every later /verify — is the company's answer.
A DENY is billed like an ALLOW for the same reason. Both produce a receipt; both are stored for seven years; both are the thing you will want in the room when someone asks who let that happen.
What is retained, and for how long#
v0.2 places one obligation on Delegus: retain, addressable by hash, every DID document snapshot, status-list credential snapshot, profile document and trust-configuration descriptor referenced by any receipt; the receipt itself; and its outcome ledger — for seven years from evaluated_at. The production evidence store uses S3 Object Lock in compliance mode: each evidence document is locked for seven years from when it was first stored, and cannot be deleted or overwritten by anyone, including Delegus, in that time. Receipts and outcomes are kept in the service's database, not in the locked store.
Outcomes are events on the receipt — fulfilled, disputed, resolved — append-only, never overwritten. Over time that becomes a record of authority, decision, reliance and outcome, each step signed by the party that took it.
Try it against a real one#
The live demo at delegus.ai/demo runs the handshake against the development sandbox and hands you real receipts, ALLOW and DENY, signed by real KMS-held keys. Take one, take the grant and proof it was issued for, and run the command above. The specification is at delegus.ai/docs/spec; the profile document whose hash appears in every receipt is served byte-for-byte at delegus.ai/profiles/delegus-base-v1.