Docs

Receipts — portable proof of a decision

Every consent write returns a signed receipt anyone can verify later, even offline, even if they don’t trust us.

Why they exist. A row in a database is only as trustworthy as the database owner. A receipt is a small, self-contained document — “on this date, this person agreed to exactly this text” — with a cryptographic signature. A regulator, an auditor, or the other side of a contract can check it themselves, years later, without calling our API and without taking our word for anything.

What you get

Every POST /v1/consent response carries a receiptId. Fetch the full receipt any time:

GET /v1/receipts/rcpt_9f2c…

{
  "receiptId": "rcpt_9f2c…",
  "issuedAt": "2024-05-01T10:03:00Z",
  "payload": {
    "subject": "<hash of the identifier>",
    "purpose": "marketing_email",
    "version": 1,
    "statementHash": "<hash of the exact wording shown>",
    "state": "GRANTED",
    "controller": "Acme Ltd"
  },
  "algorithm": "EdDSA",
  "kid": "key_2024a",
  "signature": "<base64url>"
}

The payload holds no raw personal data — the subject and the statement text are hashed, so a receipt is safe to hand to a third party.

How to verify one

The signature is standard Ed25519. Any crypto library can check it — you don’t need our SDK. Our public keys live at a fixed URL:

  1. Download the public keys from GET /.well-known/jwks.json.
  2. Pick the key whose kid matches the receipt’s kid.
  3. Re-serialise the payload with object keys sorted (JSON Canonicalization, RFC 8785).
  4. Verify signature against that bytes with the Ed25519 public key.

If it checks out, the receipt is genuine and unaltered. We rotate signing keys over time but never remove an old public key, so a receipt from years ago still verifies.

The easy paths

  • In JavaScript: await tc.verifyReceipt(receipt) — the SDK does all four steps with the browser’s Web Crypto.
  • No code: GET /v1/receipts/{id}/verify re-checks it server-side and returns { valid: true }.