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.
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
| Check | What 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.
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 = "|".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.
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
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.