Skip to main content
Every decision record in Precipiq stores the SHA-256 hash of its predecessor in its prev_hash field. The record’s own hash is then computed over a canonical serialisation that includes prev_hash. This means tampering with any historical record would break the chain at every subsequent record — detectable in O(n) by a single verify_chain call. The chain is append-only and per-org: each organisation has its own sequence, cross-tenant interference is structurally impossible, and verifying one org’s chain never touches another’s data. The first record an organisation writes uses prev_hash = "0" * 64 as a sentinel genesis. From there, every new record extends the chain by exactly one link.

How hashing works

Hashing runs on a canonical JSON encoding of eight specific fields:
action_type, agent_id, confidence, inputs, org_id,
outputs, prev_hash, timestamp
These eight fields are serialised with sort_keys=True, separators=(',', ':'), ensure_ascii=False, UTF-8 encoded, and SHA-256 digested. The hex digest is the record’s hash. Because the recipe is deterministic and language-independent, a Python SDK caller and a TypeScript caller producing the same logical record compute bit-identical hashes.

Fields not included in the hash

The following fields are stored on the record but are not part of its hash identity:
  • id — assigned server-side at insert; not under the caller’s control.
  • hash, created_at — computed or stamped during persistence; not inputs to the hash.
  • alternatives, human_in_loop, meta — caller-supplied metadata free to evolve in future versions without invalidating the chain.
If you are verifying a chain externally, re-hash using only the eight identity fields above. Re-hashing with the wider record shape produces a different digest and makes the chain appear broken when it is not.

Verifying the chain

The SDK does not yet wrap the verify endpoint. Call it directly via REST.
curl -s https://api.precipiq.dev/api/v1/decisions/chain/verify \
  -H 'X-Precipiq-Key: pq_test_demo_key_REPLACE_ME'
The endpoint walks every record in chronological order, recomputes each hash, and stops at the first mismatch. An is_valid: true response is constant-time small regardless of how many records passed — verification is cheap enough to run on a cron.

What the response contains

FieldTypeDescription
is_validbooleantrue if the entire chain is intact.
records_checkedintegerNumber of records walked before stopping.
first_broken_linkstringThe id of the first record where the hash did not match, if any.
Run chain verification on a nightly cron and alert if is_valid is ever false. A broken chain is a high-severity signal — it means a record was altered after it was written.

What the chain does not do

Timestamps are SDK-produced. A malicious writer with a valid API key could backdate a record. If you need non-repudiable time, add an external timestamping service such as RFC 3161 or a public blockchain anchor on top of the chain.
The chain proves a record exists as written and has not been altered since. Whether the decision itself was right or wrong is a business question — not something cryptographic integrity can answer.
An attacker with write access to your API key can still insert records at the tip of the chain. The hash chain makes retroactive tampering detectable, not prospective injection. Rotate API keys promptly if you suspect a compromise.
For stronger guarantees — key-anchored signing and per-org RSA export signatures — see the forensic export flow in the compliance section.