Skip to main content

Audit receipts

Every chain that runs through ProofRail produces a signed receipt when it completes. The receipt is your audit trail: it records what happened, who decided what, and when. It’s cryptographically signed so tampering is detectable, and hash-chained across your organization so deletion of any receipt is detectable too.

Why receipts matter

Compliance, debugging, and trust all eventually need the same thing: a record of what your AI agents did that you can present to a third party and have them believe it. ProofRail receipts give you that record in a form you can verify yourself, without trusting our backend to be honest about it.

What a receipt contains

When a chain completes, the backend assembles a receipt that captures the chain in full:
{
  "receipt_number": "PR-2026-00042",
  "chain_id": "chn_01HW...",
  "organization": "your-org-slug",
  "summary": "Vendor purchase workflow completed with human approval.",
  "started_at": "2026-06-13T14:22:00Z",
  "completed_at": "2026-06-13T14:24:15Z",
  "agents_involved": ["research-agent", "negotiation-agent", "email-agent", "commitment-agent"],
  "total_actions": 7,
  "cumulative_metrics": {
    "financial_exposure_usd": 11000,
    "external_communications_count": 1,
    "records_modified_count": 1,
    "external_domains_contacted": ["vendor.com"],
    "estimated_cost_usd": 0.42
  },
  "approvals": [...],
  "events_summary": [...],
  "policy_version": "2.0",
  "previous_receipt_hash": "sha256:8f3a...",
  "signature": "hmac-sha256:..."
}
The signature field is an HMAC-SHA256 over the entire structured data using a key held by the backend. The previous_receipt_hash links this receipt to the previous one for the same organization.

How signing works

When a receipt is generated, the backend:
  1. Serializes the structured data to a canonical JSON form
  2. Computes HMAC-SHA256(structured_data, server_secret) to produce the signature
  3. Stores the receipt with both the structured data and the signature
The server secret never leaves the backend. The signature can only be produced by the backend, and any tampering with the structured data invalidates the signature.

How hash chaining works

Each receipt embeds the hash of the previous receipt for the same organization. This forms a chain: receipt N+1 references receipt N, which references receipt N-1, all the way back to the first receipt (the “genesis” receipt where previous_receipt_hash is null).
Receipt 1: previous_receipt_hash = null                ← genesis
Receipt 2: previous_receipt_hash = sha256(Receipt 1)
Receipt 3: previous_receipt_hash = sha256(Receipt 2)
Receipt 4: previous_receipt_hash = sha256(Receipt 3)
...
This means:
  • Tampering with any single receipt breaks its signature
  • Deleting a receipt entirely breaks the chain at that point (the next receipt’s previous_receipt_hash no longer matches anything)
  • Reordering receipts is detectable because the hash sequence is checkable
Anyone with the receipt IDs can walk the chain and verify integrity end-to-end.

Verification — public endpoint

The most important property of ProofRail receipts is that you can verify them without authentication. The verification endpoint is public:
curl https://api.proofrail.dev/v1/receipts/{receipt_id}/verify
Returns:
{
  "valid": true,
  "receipt_number": "PR-2026-00042",
  "chain_id": "chn_01HW...",
  "generated_at": "2026-06-13T14:24:15Z"
}
If valid is true, the receipt’s signature checks out. If valid is false, the receipt has been tampered with or modified after signing. This means an auditor, regulator, or counterparty can verify a receipt’s authenticity without needing access to your ProofRail account. If you give them the receipt ID, they can check it themselves — including which chain it belongs to.

Verification — from the SDK

The SDK exposes a thin wrapper around the same endpoint:
from proofrail.client import verify_receipt

result = await verify_receipt("rcp_abc123...")

print(result.valid)           # True if the receipt is intact
print(result.receipt_number)  # Human-readable receipt number, e.g., "PR-2026-00042"
print(result.chain_id)        # The chain this receipt covers
print(result.generated_at)    # When the receipt was created
The return type is ReceiptVerifyResponse (a Pydantic model with valid, receipt_number, chain_id, and generated_at fields).

Receipt numbering

Receipts get human-readable numbers in the format PR-YYYY-NNNNN:
  • PR- is the ProofRail prefix
  • YYYY is the year the receipt was created
  • NNNNN is a zero-padded sequence number within your organization
Examples: PR-2026-00001, PR-2026-00042, PR-2026-15847. These numbers are useful for reference in your internal systems, support tickets, and compliance documentation. The underlying receipt is identified by its UUID; the number is the human-friendly label.

What this gives you

The combination of HMAC signing, hash chaining, and a public verification endpoint produces a property that’s unusual for SaaS audit logs: you don’t have to trust ProofRail’s backend to be honest about your own data. If you ever need to prove what your agents did — to a regulator, to a customer, to your security team, to opposing counsel — the receipt is the proof. The math works the same regardless of whether ProofRail is still in business, whether your account is still active, or whether anyone else trusts us. That’s the design goal of the receipt system. It’s not “we’ll keep good records,” it’s “you don’t need to take our word for it.”

Where to go next

Chain-level governance

Why we track the whole workflow, not just individual calls.

Policies

How decisions get made before receipts get generated.

Human approval

How approvals get recorded in receipts.

SDK reference

The full API surface, including verification.