Guide ยท Developers
Audit & trust (Developers)
How our hash-chain audit trail keeps your ledger tamper-evident, replayable, and provable.
What is the audit chain?
Every transaction stores a content hash and a link to the prior transaction (by recorded_at order). This forms a forward-only chain โ any change would break the links, so tampering is evident.
How it works
- Deterministic hashing: the transaction payload (metadata + entries) is hashed consistently.
- Linking: each new transaction records the previous transaction's hash as its pointer.
- Append-only: recorded_at enforces chain order; backdated effective dates are allowed without breaking continuity.
- Replayable: you can recompute the chain end-to-end to verify integrity.
Why it matters
- Trust: partners and auditors can verify no silent edits occurred.
- Safety: retries are safe โ idempotent posting returns the same result for the same payload.
- Transparency: clear ordering and linking make investigations straightforward.
Running an audit
From the Rails console, run the audit service to verify the chain:
result = Ledger::Public::Services::AuditManager.audit_chain(ledger_id: "your-ledger-id")
if result.success?
puts "Chain intact across #{result.checked_count} transactions"
else
puts "Divergence at #{result.divergence_at}: #{result.errors.first[:message]}"
end
You can scope audits to a point in time or run full history to support compliance reviews.
Independently verifying a hash
You can reproduce any transaction's strong_content_hash without trusting our system. The hash is a SHA-256 digest over a canonical JSON payload. The event log preserves the exact payload used at posting time โ replay it to confirm our stored hash matches what you compute independently.
What strong_content_hash covers
- sequence_number, ledger_id, effective_at_epoch_us, description, reference_number, idempotency_key, metadata, adjusting
- entries โ sorted array of: account_id, direction, amount_cents, currency
- previous_hash โ the hash of the immediately preceding transaction
Note: effective_at is converted to microseconds since Unix epoch (effective_at_epoch_us) before hashing. transaction_id is not included in the hash.
sequence_number guarantees
Every domain event carries a ledger-scoped sequence_number assigned at write time. Numbers are monotonically increasing and never reused. A gap or duplicate in the sequence would indicate a missing or injected event.