Verification
The Hash Chain
Each entry is hashed with SHA-256 over canonical JSON and references the hash before it. Altering any entry breaks every hash after it.
The construction
Each ledger entry is serialized to a canonical JSON form and hashed with SHA-256. Included in the material that gets hashed is prior_hash — the hash of the entry immediately before it. Each entry therefore commits to its predecessor, and by induction to every entry before that.
The consequence is the property the whole system rests on. Alter any field of any entry and that entry's hash changes. The next entry's prior_hash no longer matches, so the next entry's hash is now wrong too, and so on to the end of the chain. There is no way to change one thing quietly: a single edit invalidates every entry that follows it, and the break is arithmetic that anyone can reproduce.
The algorithm identifier is sha256-canonical-json-v1, and the public endpoints return it in an X-Ledger-Algo header so that a client is never guessing at the construction it is checking.
What goes into the hash
Exactly nine fields, and no others:
{
actor_email,
actor_id,
content,
created_at, // normalized to ISO-8601 first
event_type,
prior_hash,
seq,
tenant_id,
v: 1 // preimage version tag
}The set is worth reading closely, because what is inside determines what is protected. content means the substance of the event cannot change. actor_email and actor_id mean attribution cannot be reassigned to someone else. seq and prior_hash mean entries cannot be reordered or spliced out. created_at means the recorded time cannot be adjusted after the fact — though see Verification for the important limit on what that establishes.
The v tag versions the preimage itself, so that if the construction ever needs to change, old entries remain verifiable under the rules they were written with.
Canonical JSON
Hashing JSON requires care, because the same data has many valid serializations. Key order, whitespace, and numeric formatting all vary between languages and libraries, and each variation produces a completely different SHA-256 — which would make a chain that only verified on the machine that wrote it.
Canonicalization removes the ambiguity: object keys are sorted lexicographically, array order is preserved as meaningful, and values that have no stable representation — cycles, non-finite numbers — are rejected outright rather than coerced.
Timestamps get specific handling. created_at is normalized to an ISO-8601 string before hashing, because the database returns timestamps in a different textual shape than the one originally written. Without normalization a chain would verify at write time and fail on read — the same instant in time, serialized two ways, producing two hashes. This class of bug is the most common way hash chains break in practice, and it does not break loudly.
The genesis entry
Every ledger begins with a tenant.genesis entry at seq 0. It has no predecessor, so its prior_hash is sixty-four zeros — a fixed sentinel that marks the start.
This makes the chain's start unambiguous. A verifier walking a ledger knows it has reached the beginning when it finds the zero sentinel, so a chain cannot be presented as complete while concealing that entries were removed from its head.
Nightly checkpoints
A scheduled job runs once a day and records a checkpoint for each ledger: its current sequence number, its current head hash, and the number of entries at that moment. The checkpoint is published at a stable URL. The intent is to pin the chain's state at a known time, so that a later attempt to present a different history is contradicted by a record made before it.
Not a blockchain
A hash chain and a blockchain are related but not the same, and the difference is worth being exact about rather than trading on the ambiguity.
This is a hash chain: entries linked by cryptographic hashes, so that alteration is detectable. There is no token, no consensus mechanism, no mining, no distributed validator set, and no peer-to-peer network. Entries are per-event, not batched into blocks — despite the word “block” appearing in some display copy in the application, there is no block structure in the data.
What a blockchain adds over a hash chain is decentralized agreement about which history is the real one, purchased at considerable cost. OpenBooks does not buy it. The result is a simpler, older, better-understood construction that makes exactly one honest claim — this record has not been altered since it was written — and that claim is checkable by anyone. What it does not claim is set out in Verification, and readers who care about the difference should read that page rather than this one.