Skip to main content

Canonical JSON

For the hash chain to work, the same event must always produce the same byte sequence. Trailproof uses canonical JSON — a strict, deterministic serialization format.
Canonical JSON is what makes cross-SDK parity possible. Python and TypeScript produce identical byte strings for the same event, so they produce identical hashes.

Rules

Trailproof’s canonical JSON follows these rules:
  1. Keys sorted alphabetically — recursive for nested objects
  2. Compact format — no whitespace (separators: , and :)
  3. Exclude hash and signature — these fields are not part of the hash input
  4. Exclude null/None fields — omitted entirely, not serialized as null
  5. UTF-8 encoding — consistent byte representation

Example

Given this event:
The canonical JSON is:
Notice:
  • Keys are sorted alphabetically (actor_id before event_id before event_type…)
  • hash and signature are excluded
  • trace_id and session_id are excluded because they’re null
  • Payload keys are also sorted (ip before method)
  • No whitespace

Nested Objects

Canonical JSON sorts keys recursively. If your payload contains nested objects, their keys are also sorted:
Python

Hash Computation

The hash is computed by concatenating prev_hash with the canonical JSON, then applying SHA-256:
Both prev_hash and canonical JSON are plain strings — the concatenation is string concatenation, then the result is hashed.
Changing any field value — even adding a space to a string — produces a completely different hash. The canonical format ensures this is deterministic and detectable.

Next Steps

Event Envelope

The 10-field structure that gets serialized.

Hash Chain

How canonical JSON feeds into the hash chain.