Receipts
What a signed consent receipt contains, how it is signed with Ed25519, and how a third party verifies one without the deployment.
A receipt is the portable form of the proof. It is one signed JSON document that you can hand to an auditor, a regulator or the person. They can check it without access to your project.
What a receipt contains#
- The record. Its id, the subject hash and type, the status, the environment, the interaction, the jurisdiction and the decisions as they stand. For consent brought in from earlier, the basis you stated and the notice reference. For consent taken by your staff or on paper, how it was taken.
- The subject fingerprint, unless the record is erased.
- The form. Its id, version, content hash and name, and the banner wording in the form's default language.
- The sealed chain. Each event's id, number, type, time,
proofHashandpreviousProofHash. - The replay result. The deployment replays every seal with its chain key when it issues the receipt, and writes the outcome into
proof.verification. - Anchor proofs. For each event whose day is anchored, an inclusion proof and the signed anchor.
The purposes as shown, with their descriptions, data and partners, are not copied in. Neither are the other languages. The content hash covers them, so you compare it with that form version in the console.
How it is signed#
{
"receipt": {
"specification": "piedap-consent-receipt/2.0",
"consentId": "cns_…"
},
"signature": "base64url signature over canonical(receipt)",
"algorithm": "Ed25519",
"keyId": "ed…",
"publicKeysUrl": "https://console.dpconsent.com/consent/sdk/v1/keys"
}The signature is Ed25519 over the canonical JSON of receipt. Canonical JSON has its keys sorted at every depth. The public key is published as a JWK Set at /sdk/v1/keys, and reading it needs no account.
Where a receipt comes from#
| Source | Is the export logged |
|---|---|
| Records, open a record, Export signed receipt | Yes, as an EXPORTED event |
An access request, or POST /sdk/v1/subjects/export |
Yes |
GET /sdk/v1/consent/{consentId}/verify with a key of the project |
No. It only reads |
Reading does not change evidence. The EXPORTED event is outside the sealed chain.
How a receipt is verified#
There are three ways to check a receipt. The console has Verify a receipt under Records. The API has POST /sdk/v1/receipts/verify, which needs no key. Both work the signature, the links between events and every inclusion proof out again from the document. Nothing in the receipt is trusted.
The third way is offline, and needs nothing from the deployment at the time of the check:
- The verifier holds a copy of the JWK Set, fetched once and kept. The key is
kty: OKP,crv: Ed25519. - The signature is verified over
canonical(receipt)with the key whosekidequalskeyId. - The chain links are checked. The first event's
previousProofHashis 64 zeros, and each later one equals theproofHashbefore it. The lastproofHashequalsreceipt.proof.proofHash. - For each entry in
receipt.proof.anchors, the leaf is rebuilt and the Merkle root is recomputed from the audit path. - The anchor's statement hash and signature are checked.
- Each
anchorHashis compared with the copy your webhook received that day.
The last step is the one that does not rely on the operator. Daily anchors explains why.
Who the receipt is about#
A receipt names the person only by a keyed hash and by the subject fingerprint. You recompute the fingerprint from your own identifier and your project's fingerprint key. A match shows the receipt is about the person it is said to be about. An erased record's receipt carries no fingerprint.
Limits#
- A receipt alone does not re-run the seals. That needs the chain key. A third party relies on the signed
proof.verification. - The Node helper checks the signature only.
verifyReceiptdoes not walk the chain or the anchors. Those steps are yours to run, or use the API. - A changed signing key. The deployment publishes and checks with its current signing key only. After a change, older receipts still verify offline against the key you saved. They no longer pass the console or API check.
- Receipts signed with HMAC. A receipt issued before signing moved to Ed25519 carries
"algorithm": "HMAC-SHA256". Only the deployment that issued it can check it. - A test receipt is not evidence. Its
environmentistest.