Offline verification
The whole point: anyone with a verify URL can prove a receipt is real without trusting Marturia, without an account, and without a network call to us at verify time.
What "verified" means
The verifier checks four things:
- Signature. The Ed25519 signature on
receipt_hashvalidates against the published public key. - Hash integrity. Recomputing the canonical-JSON SHA-256
of the payload + metadata produces exactly
receipt_hash. - Sequence. The
receipt_seqis monotonic and theprev_receipt_hashmatches the chain. - Witness cosigning (when present). At least one independent operator node has cosigned the Merkle root that anchors this receipt.
The verifier — for your auditors
marturia-verify is a small open-source Python tool. It does
no network calls; everything runs offline against the JSON you give it.
pip install marturia-verify
The whole flow your auditor would run:
# 1. Pull the receipt from the verify URL (one-time network call).
curl -s https://marturia.dev/v1/verify/2/98 > receipt.json
# 2. Extract the public key (already inside the receipt response).
jq -r .public_key receipt.json > pubkey.hex
# 3. Verify offline. No network. Auditable source.
marturia-verify --receipt receipt.json --pubkey pubkey.hex
Successful verification looks like:
VALID: signature valid; receipt_hash matches; sequence 54 of tenant 2
Tampered receipt looks like:
INVALID: receipt_hash mismatch — payload was modified
What the URL returns
GET https://marturia.dev/v1/verify/{tenant_id}/{receipt_id}
returns a JSON envelope your auditor can save and re-verify any time:
{
"tenant_id": 2,
"receipt_seq": 54,
"agent_name": "purchase_approver",
"agent_run_id": "run_demo_001",
"created_at": "2026-05-09T18:42:11.337Z",
"signing_kid": "t2-v1",
"receipt_hash": "803e993d40faef9071dd0d458d6b5382...",
"prev_hash": "a609ed8ce163599bd7c076eda757d2b7...",
"signature": "5fd2e0a14a6d5c9c...",
"public_key": "9fc982fe34745307f97bdf9c...",
"payload_canonical_bytes": "<base64 of canonical JSON>"
}
What an auditor doesn't have to trust
- Marturia. The cryptographic chain is independently verifiable. We can't forge a receipt without the tenant's private key.
- The customer. The customer can't tamper with a
receipt after the fact; the chain catches it. They also can't sign new
receipts retroactively because each receipt's payload is hashed
together with its
created_atand prior chain state. - The verify URL. The URL is convenience. The auditor can save the JSON and re-verify it offline at any time.
What an auditor still trusts
- The tenant's public key. Anyone with the private key can sign a receipt that verifies. If the customer's signing-key infrastructure is compromised, the chain still holds for everything signed before the compromise — but new receipts after the breach carry the attacker's signature.
- Witness cosigning mitigates this: a successful compromise requires breaking both the customer's keys and independently colluding with the witness operator network.
Sharing a verify URL
Open the dashboard's Receipts tab → click any receipt → "Cryptographic verification" panel has the verify URL with a one-click copy. Paste into Slack, email, a PDF, whatever. The recipient doesn't need an account.
Building your own verifier
The verifier is intentionally tiny — under 300 lines of Python. The design goal is "an auditor can read it in an afternoon." If your audit team needs to implement it in another language, the canonical-JSON + Ed25519 contract is documented in the marturia-verify README, and the source is reference enough.
Listing the chain
To verify the chain in bulk (every receipt for a tenant, in order),
marturia-verify also accepts a directory of receipt JSON
files and walks them sequentially:
marturia-verify --chain ./receipts/ --tenant-pubkey pubkey.hex