Verifying a Sequesign receipt.
A Sequesign receipt is designed to be verified offline by anyone who has it. This post walks through what verification actually does, what the receipt package looks like, and what a minimal verifier needs to check.
What a receipt package contains
A Sequesign receipt is a directory or zip file with a small, fixed structure:
receipt.sequesign/
receipt.json receipt envelope with metadata
actions.jsonl one action record per line
evidence/ hashed evidence content
act_001_task_created.json
act_002_policy_checked.json
...
attestations.json agent, witness, human, counterparty signatures
keys/ public keys for verification
agent.pub.pem
witness.pub.pem
verification-report.json output of the last verifier run (optional)
The receipt envelope at the top is a single JSON object that names the chain, declares the receipt mode (freeform, schema-validated, or profile-constrained), references the schemas and profile if applicable, and lists the final chain state. Everything else in the package is content that the envelope references by hash.
What the verifier checks
Verification is deterministic. Given the same receipt and the same public keys, every verifier in the world produces the same report. There is no fuzziness, no model inference, no scoring. The verifier returns a structured report with flags for each property it checked.
The checks, in order:
1. Chain integrity. The verifier walks the chain from the initial state forward. For each action, it computes the action record hash from the canonical action record, computes the next chain state from the previous state and the action record hash using the chain extension function, and confirms the new state matches what is recorded. Any mismatch fails the receipt with reason chain_state_mismatch.
2. Evidence hashes. For each action, the verifier canonicalizes the corresponding evidence file, hashes it, and confirms the hash matches what the action committed to. Any mismatch fails with reason evidence_hash_mismatch.
3. Agent signatures. Each action carries an agent signature over a canonical message that includes the action record hash, the previous chain state, and the new chain state. The verifier confirms the signature verifies under the agent's declared public key. Failure here means the agent did not actually sign this action, or the message has been altered.
4. Witness signatures. Each chain extension carries a witness attestation. The verifier confirms the witness signature verifies under the witness's published public key. The witness key is discovered via a well-known endpoint, or for offline verification, it is included in the receipt's keys directory.
5. Schema validation, if requested. If the receipt mode is schema-validated or profile-constrained, the verifier loads each declared schema, confirms its hash matches what the receipt committed to, and validates each evidence object against its schema. Failure here means the evidence does not match the shape it claimed to follow.
6. Workflow profile, if requested. If the receipt is profile-constrained, the verifier loads the declared profile and confirms the action sequence satisfies the profile's rules. This catches missing required actions, prohibited transitions, and other workflow-level errors.
7. Human approval attestations. If the workflow includes human approvals, the verifier confirms each approval attestation verifies under the approver's public key, and that the approver's identity matches what the workflow required.
8. Counterparty attestations. If any actions include counterparty attestations, the verifier confirms those signatures and resolves the counterparty public key via the declared discovery method.
9. Unsupported-claim warnings. The verifier inspects evidence for claims marked as unsupported and surfaces them as warnings on the report. These warnings do not fail the receipt; they make the trust gap visible.
What the report looks like
The verifier's output is structured for both human and machine reading:
{
"valid": true,
"receipt_id": "rcpt_8f3a4c2e91d7b6a8",
"chain_id": "chn_7f3a4c2e91d7b6a8",
"verification_level": "L4_HUMAN_APPROVED",
"mode": "profile_constrained",
"profile": "sequesign.invoice_payment.v0.1",
"flags": {
"hash_integrity": true,
"sequence_integrity": true,
"schema_validation": true,
"workflow_profile": true,
"agent_identity_bound": true,
"witness_verified": true,
"policy_bound": true,
"human_approval_verified": true,
"counterparty_confirmed": null
},
"warnings": [],
"reason": null
}
The flags are the truth. The verification_level is a summary label derived from the flags. null in a flag means "not requested," not "failed."
When verification fails, the report includes a specific reason and pinpoints the affected action:
{
"valid": false,
"reason": "evidence_hash_mismatch",
"action": {
"sequence": 3,
"action_type": "llm_invoice_reviewed"
},
"expected_evidence_hash": "a3f7b8c2e91d4f6a2c8e1f3b9d6c5e7f",
"computed_evidence_hash": "b2e8f4a7c93d5a1e3b7c9f2d8a4e6c1b"
}
Running the verifier yourself
The reference implementation includes a verifier you can run locally on any receipt. The verifier is offline by design; it reads the receipt package and the public keys it needs, and it produces a report without contacting any external service. This is intentional. A receipt is supposed to be verifiable by anyone who has it, including someone with no relationship to Sequesign and no internet connection.
The web demo at sequesign.com runs this same verifier server-side and renders the report in the browser. There is no model inference, no heuristic, no magic. What you see in the demo is exactly what a local verifier produces from the same inputs.
The SDK, which is in progress, will expose the verifier as a one-line import so any system can verify receipts as part of its own workflow. Until then, the reference implementation is the canonical source.