Every snapshot is hashed using RFC 8785 canonicalization and SHA-256. Snapshots chain to each other so you can verify any history is untampered.
Identity data is only as trustworthy as the guarantees that it has not been altered after the fact. Tally builds those guarantees into every snapshot using cryptographic hashing and a tamper-evident hash chain. Any modification to any historical snapshot — even changing a single character in an attribute value — produces a hash mismatch that you can detect by re-computing hashes yourself. No trust in Tally’s infrastructure is required to verify your data.
When Tally stores a snapshot, it computes a deterministic hash of the envelope using a two-step process based on the RFC 8785 JSON Canonicalization Scheme and SHA-256:Step 1 — RFC 8785 canonicalization. The envelope JSON is serialized using the JSON Canonicalization Scheme (JCS, RFC 8785). JCS produces a byte-for-byte identical UTF-8 serialization regardless of the original key ordering, whitespace, or number formatting in the source JSON. This eliminates any ambiguity about what bytes are being hashed.Step 2 — SHA-256. The SHA-256 digest is computed over the UTF-8 bytes of the JCS-serialized envelope. The output is encoded as a lowercase hexadecimal string. This value is the envelope_hash.
The integrity block itself — if present — is excluded from the hash input so that embedding the hash does not create a circular dependency.
The hash is computed solely from the envelope JSON: envelope_version,
snapshot_id, snapshot_version, generated_at, subject, attributes,
evidence, and attribute_paths (if present). Fields like audit and
diff are excluded from the canonical snapshot object. Tenant IDs, grant
records, and all other authorization metadata are never part of the hash
input — authorization state changes never invalidate snapshot hashes.
Each snapshot v2 and later stores the envelope_hash of its immediate predecessor in the integrity.prev_envelope_hash field. This creates a tamper-evident chain:
If an attacker modifies snapshot v1 — changing, say, an ownership percentage — the envelope_hash of v1 changes. But snapshot v2’s prev_envelope_hash still points to the original hash. Recomputing v2’s chain verification immediately reveals the mismatch.An optional chain_hash strengthens this further:
The export file contains every snapshot envelope plus its stored envelope_hash and prev_hash. Use the tally CLI to re-verify all hashes locally without making any further API calls:
tally verify-ledger export.json
Verifying ent_acme_001 (3 snapshots)... v1 3f0b2c2c ✓ hash OK (root, no prev) v2 9c7d1e3f ✓ hash OK ✓ chain OK v3 a8b4c2d1 ✓ hash OK ✓ chain OKAll 3 snapshots verified. Chain is intact.
The CLI re-computes SHA-256(JCS(envelope)) for every snapshot from scratch and checks that each prev_hash matches the preceding snapshot’s computed hash. No stored values are trusted — every hash is independently re-derived.
Include the exported file in your compliance archive alongside the CLI
verification output. This gives auditors a self-contained proof that the
identity data they are reviewing has not changed since it was written, with
no dependency on Tally’s live systems.
Normal reads where integrity is not the focus — fastest response
hash
?verify=hash
Spot-check that a single snapshot’s stored hash is correct
chain
?verify=chain
Full tamper-evidence check across the snapshot lineage
verify=chain triggers a walk of the full snapshot history for the subject
and is more expensive than verify=hash. For high-throughput read paths,
prefer verify=none and run periodic chain verification offline using the
export + CLI workflow.