Skip to main content

How It Works

Every event in Immutable is part of a per-workspace SHA-256 hash chain. Each event’s hash is computed from all of its fields plus the hash of the previous event. Change any field on any event, and every subsequent hash in the chain breaks.
The first event in a workspace has previous_event_hash set to null. Every subsequent event references the hash of its predecessor.

The 22 Hashed Fields

Every field on the event is included in the hash computation. Nothing is excluded. The hash payload is constructed by concatenating all 22 field values in a deterministic order, then computing SHA-256 over the result.
Deterministic ordering matters. Metadata keys are recursively sorted and targets are sorted by type then id before hashing. This guarantees identical hashes regardless of JSON key order or array ordering in the database.

How Tampering Is Detected

Field modification (hash_mismatch)

If any field on an event is modified — even a single character in the metadata — the recomputed hash will not match the stored hash.

Event insertion or deletion (chain_break)

If an event is inserted into or deleted from the chain, the previous_event_hash on the next event will not match the hash of what now precedes it.

Chain Ordering

Events are chained by created_at (server-side timestamp with microsecond precision), not occurred_at (client-provided time). This guarantees a linear, consistent chain even when clients submit events with out-of-order timestamps.
When events are ingested via the batch endpoint, each event’s created_at is offset by 1 microsecond to preserve insertion order. Without this, batch events would share the same timestamp, and UUID ordering would not match insertion order — causing false chain breaks during verification.

Concurrency Safety

Multiple API requests can ingest events into the same workspace concurrently. To prevent conflicting hash chains, Immutable uses PostgreSQL advisory locks:
This transaction-scoped lock ensures only one event is hashed at a time per workspace. The lock is held only for the duration of the hash computation and event insertion, then released automatically when the transaction commits.

Manually Recomputing a Hash

You can independently verify any event hash. The hash payload is the concatenation of all 22 fields in this order:
The pipe character (|) is used as a field delimiter. Null fields are represented as empty strings. The resulting hash is a 64-character lowercase hex string.

Verifying the Chain via API

Use the verify endpoint to validate the chain programmatically:
See Verify Your Events for a complete verification walkthrough.
Legacy events ingested before hash chain activation have nullable hash fields. The verify endpoint skips these events and does not report them as breaks.