Methods · consent ledger

Provenance you can verify yourself.

This page publishes the exact bytes we hash, the order we hash them in, and the check that returns the chain, so you can recompute a consent record yourself.

Delivery formats: what we do and do not offer

Five containers ship. MCAP and WebDataset do not. What arrives, and in what container.

Verify a delivery

You can check a receipt id against a published snapshot of the chain on its own page: check a consent receipt. It runs entirely in your browser and sends us nothing.

This is the part that matters on the day you are asked to justify a dataset. There are two routes to it, and the second one does not need us at all.

The checks below run on our engine, and a Commissioning lab can ask us for their result. The route that needs nothing from us is reproducing the hash yourself from the published pre-image, further down this page.

Verification checks

CheckWhat it returns
Verify the whole chain Recomputes every hash and checks the linkage. Returns the genesis hash, the head hash, the number of entries checked, and, if broken, the exact sequence number and reason.
One receipt's history The full append-only history of one receipt, oldest first: granted, then withdrawn, each entry with its own hash and link.

You do not have to take our word for the result, and you do not have to use our check at all; recompute the hashes yourself from the published specification and compare.

How it works

Append-only

Withdrawal of consent appends a REVOKED entry; it never edits the original. The grant and the withdrawal both remain in the ledger, each independently hashed. That matters because "we deleted the record" and "we can show you exactly when consent was withdrawn" are not the same claim, and only the second one is auditable.

Append-only is enforced by the database itself (consent_ledger_no_update, consent_ledger_no_delete abort any UPDATE or DELETE), not merely by convention in application code.

The footage is a different matter, and it does get deleted. Revoking a receipt runs a deletion stage over the raw video, the redacted copy, the working files and the annotation, and halts any processing job that was mid-flight. The ledger entry recording that you withdrew survives (it is the evidence the request was honoured) along with a fingerprint of the bytes that were removed. What the ledger keeps is the proof; what it does not keep is the clip.

Hash-chained

Each entry's hash includes the previous entry's hash. Removing or reordering an entry breaks every hash after it. The first entry links to a published genesis value, so an auditor starts the walk from a known constant rather than from something we supply.

Genesis hash · SHA-256 · 64 hex characters
0000000000000000000000000000000000000000000000000000000000000000

That is GENESIS_HASH = "0" * 64, a constant, not a value we mint, so the starting point of the walk is not ours to change. Scroll the block sideways rather than reading a wrapped copy: a hash broken across lines cannot be compared character-by-character, and that comparison is the whole exercise.

Reproduce it without us

SHA-256 is computed over these fields, in this order, joined with |. This block is the complete specification: given the rows, you can recompute every hash in any language and compare, with no cooperation from us and no access to anything we control.

Pre-image · field order
pre_image = "|".join([
    sequence,
    receipt_id,
    worker_id,
    task_id,
    status,
    consent_timestamp,
    revocation_timestamp,
    device_id,
    location_city,
    dpdp_pii_policy,
    prev_hash,
])

sha256_audit_hash = sha256(pre_image.encode("utf-8")).hexdigest()

Every semantic field is inside the pre-image, including status and revocation_timestamp, the two that record a withdrawal. Altering any one of them changes the hash. The separator cannot occur in the generated identifiers, so two different records cannot collide by concatenation.

One entry, worked end to end

A specification you cannot run is a specification you have to trust, so here is a complete entry: every field, the pre-image they join into, and the hash our service produces from it.

The record is an example and its identifiers say so. A real row carries a Capture Partner's id, their device and their city; publishing one would be the exact disclosure this ledger exists to prevent. The hash is not an example. It comes from the same compute_entry_hash that hashes production records, and this page refuses to build if the field order printed above stops reproducing it.

Example entry · pre-image · SHA-256
sequence              1
receipt_id            CONSENT_RCPT_EXAMPLE01
worker_id             USR_EXAMPLE_0001
task_id               TASK_EXAMPLE_0001
status                ACTIVE
consent_timestamp     2026-08-17T09:30:00Z
revocation_timestamp  (none)
device_id             DEV_EXAMPLE_0001
location_city         Bengaluru
dpdp_pii_policy       Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.
prev_hash             0000000000000000000000000000000000000000000000000000000000000000

pre_image  (the eleven fields above, joined with |)
1|CONSENT_RCPT_EXAMPLE01|USR_EXAMPLE_0001|TASK_EXAMPLE_0001|ACTIVE|2026-08-17T09:30:00Z||DEV_EXAMPLE_0001|Bengaluru|Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.|0000000000000000000000000000000000000000000000000000000000000000

sha256_audit_hash
8456edb7402758c2106b81ce96628a601dff3525e1e4ff9ee1820de8d5b9761b
Reproduce it: no dependencies, nothing of ours
printf '%s' '1|CONSENT_RCPT_EXAMPLE01|USR_EXAMPLE_0001|TASK_EXAMPLE_0001|ACTIVE|2026-08-17T09:30:00Z||DEV_EXAMPLE_0001|Bengaluru|Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.|0000000000000000000000000000000000000000000000000000000000000000' | shasum -a 256

That prints 8456edb7402758c2106b81ce96628a601dff3525e1e4ff9ee1820de8d5b9761b. Change one character of one field and it does not, which is the entire mechanism, in a form you can run on your own machine in the next thirty seconds.

Or check it here, without leaving the page

Your browser hashes the pre-image with the Web Crypto API it already ships, and compares the result to the digest above. Nothing is sent anywhere, and nothing about the answer comes from us.

The exact string being hashed
1|CONSENT_RCPT_EXAMPLE01|USR_EXAMPLE_0001|TASK_EXAMPLE_0001|ACTIVE|2026-08-17T09:30:00Z||DEV_EXAMPLE_0001|Bengaluru|Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.|0000000000000000000000000000000000000000000000000000000000000000

Published digest

8456edb7402758c2106b81ce96628a601dff3525e1e4ff9ee1820de8d5b9761b

And the withdrawal of that same consent

A revocation is appended, not applied in place. The grant above stays in the chain and this entry follows it, so both remain verifiable for good. Its prev_hash is the digest above — that is what links them, and it is why neither can be quietly removed later.

The exact string being hashed
2|CONSENT_RCPT_EXAMPLE01|USR_EXAMPLE_0001|TASK_EXAMPLE_0001|REVOKED|2026-08-17T09:30:00Z|2026-08-24T11:05:00Z|DEV_EXAMPLE_0001|Bengaluru|Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.|8456edb7402758c2106b81ce96628a601dff3525e1e4ff9ee1820de8d5b9761b

Published digest

4aa3751fd2f217822efbbf704cebdede98bfcb095a2574a29a61238aff3cdc6e

This is a worked example, not evidence that a revocation has occurred. What the withdrawal then triggers — deletion of every derived artifact — is proved separately by a fresh disk scan, not by this hash.

11 assertions run against the shipped hash function, not a copy of it, and 11 pass. Four of them matter more than the rest: they bypass the database triggers entirely and tamper with the file on disk, because an attacker with disk access would. The chain still catches it.

What the chain proves

Recomputing the chain catches an entry that was edited, moved or deleted, as long as a later entry follows it. The newest hash is not recorded with any third party, so deleting the newest entries, or editing an entry and re-hashing every entry after it, would still verify.

How the data these records describe is protected: data protection, in the privacy notice.