Evidence receipts
Every decision produces a signed receipt; every later state change (approval, execution, cancellation) produces a new receipt chained to the previous one by hash. Receipts are the product's proof layer: provider-neutral, exportable JSON, verifiable without trusting mnd8t's database.
Shape
{
"receipt_version": "1.0",
"receipt_id": "evd_…",
"organisation_id": "org_…",
"principal": { "type": "ORGANISATION", "reference": "acme_ltd" },
"agent": { "id": "agt_…", "external_id": "procurement_agent_12" },
"mandate": { "id": "mnd_…", "version": 3, "policy_hash": "sha256:…" },
"intent": {
"id": "int_…", "amount_minor": 84000, "policy_amount_minor": 84000,
"policy_currency": "GBP", "asset": "USDC",
"counterparty": "supplier_acme_cloud", "destination": "0x…",
"purpose": "cloud_infrastructure", "submitted_at": "…"
},
"decision": {
"status": "ESCALATE", "effective_status": "ESCALATE", "mode": "ENFORCE",
"reasons": ["HUMAN_APPROVAL_REQUIRED"], "decided_at": "…"
},
"approval": { "status": "APPROVED", "approver_reference": "usr_…", "comment": null, "acted_at": "…" },
"execution": { "status": "CONFIRMED", "provider": "SIMULATED", "external_reference": "tx_…", "confirmed_at": "…", "error": null },
"revocation": null,
"previous_receipt_hash": "sha256:…", // null on the first receipt
"signature": { "algorithm": "Ed25519", "key_id": "key_…", "value": "base64…" }
}
Canonicalisation
Signatures cover the canonical form of the receipt without its signature
field: object keys sorted by UTF-16 code units, no whitespace, standard JSON
string escaping, and integers only (the serialiser rejects floats — all
money is minor units). This is a strict subset of RFC 8785 JCS sufficient for
receipt content. The same algorithm is implemented server-side and in the
@mnd8t/sdk client, so a receipt verifies identically wherever you check it.
Verifying
- Server:
GET /v1/evidence-receipts/{id}/verify - SDK (local, independent):
const envelope = await client.receipts.get(id); const { valid, reason } = await client.receipts.verify(envelope.receipt); - Your own code: the receipt is canonicalised JSON with an Ed25519 signature — any standard crypto library can verify it against the published public key.
Public keys are served at GET /.well-known/mandate-keys.json
({ keys: [{ key_id, algorithm, public_key_pem }] }); pick the key matching
signature.key_id.
Any field change — an amount, a reason code, a timestamp — breaks
verification. The previous_receipt_hash chain makes the sequence of states
for one decision tamper-evident as a whole.
Key management
Signing keys are Ed25519, loaded from a secret manager and never committed anywhere. Each key has an id that appears on every receipt it signs, so receipts stay verifiable across a key rotation as long as the retired public key remains published.