Skip to content

End-to-End Walkthrough (TypeScript)

This walkthrough takes you through the complete receipt lifecycle: emit a receipt event from a TypeScript agent, list the stored chain, and verify its integrity from the CLI. By the end you will have seen the full loop that makes receipts meaningful — not just that a receipt was written, but that it can be independently verified later.

Prerequisites: the daemon is installed and running. If not, complete Daemon Setup first.

Fresh chain assumed: sequence numbers below (1, 2) are correct when the current day’s chain has no prior receipts. If you have already emitted receipts, run obsigna receipt list to find the current tail and substitute the actual sequence numbers.


Terminal window
npm install @obsigna/sdk-ts

Create emit.ts:

import { DaemonEmitter } from '@obsigna/sdk-ts';
async function main() {
const emitter = new DaemonEmitter();
try {
const err = await emitter.emit({
channel: 'walkthrough',
tool: { name: 'fs.write_file' },
decision: 'allowed',
});
if (err) throw err;
console.log('Receipt emitted.');
} finally {
emitter.close();
}
}
main().catch(console.error);

Run it. This walkthrough uses tsx, a zero-config TypeScript runner — npx fetches it on first use, so this step needs network access:

Terminal window
npx tsx emit.ts

Any TypeScript runtime works just as well: bun emit.ts, Node 22.11+‘s native node --experimental-strip-types emit.ts (22.11 is the SDK’s minimum), or compile with tsc and run the emitted .js with node.

The daemon receives the event over its Unix socket, signs it with its Ed25519 key, chains it to any previous receipt in the same chain, and persists it to the SQLite store. The emitter itself never touches the key.

channel is the integration namespace for this emitter — it becomes the prefix of the action type stored in the receipt (walkthrough.fs.write_file here). Use a stable, descriptive identifier for your integration: mcp, claude-code, my-agent, and so on.

emit() returns null on success or an Error on failure — an EmitTransportError when the daemon is unreachable, a plain Error for caller bugs such as a missing tool name or invalid decision. close() is synchronous and releases the socket.


Terminal window
obsigna receipt list

You should see one row: sequence 1, tool fs.write_file, and a chain id that is today’s UTC date (e.g. 2026-06-13) — the daemon names each day’s chain by date.

To inspect the full receipt:

Terminal window
obsigna receipt show 1

With a single chain in the store, show auto-detects it; pass --chain-id only when the store holds more than one chain. This prints a human-readable table of the receipt’s fields. To see the full W3C Verifiable Credential JSON — including the proof block with the Ed25519 signature — pass --json:

Terminal window
obsigna receipt show 1 --json

In the JSON output, credentialSubject.chain.previous_receipt_hash is null for the first receipt in a chain — it has no predecessor. The hash linkage becomes visible once there is a second receipt to point back at it.


Terminal window
obsigna receipt verify

Example output (on a fresh chain):

Chain 2026-06-13: VALID (1 receipt)

The verifier re-derives the hash of each receipt, checks the signature against the daemon’s public key, and confirms the previous_receipt_hash in each receipt matches the hash of its predecessor. A broken hash link or bad signature produces a non-zero exit and a description of the failure.

The receipt DB and public key are self-contained. Copy them anywhere and verify without a running daemon:

Terminal window
obsigna receipt verify \
--db ~/.local/share/agent-receipts/receipts.db \
--public-key ~/.local/share/agent-receipts/signing.key.pub

Rerun emit.ts:

Terminal window
npx tsx emit.ts
obsigna receipt verify

Example output (count is one higher than after the first emit):

Chain 2026-06-13: VALID (2 receipts)

Run obsigna receipt show 2 (or substitute the sequence of your second receipt from obsigna receipt list) — unlike receipt 1 (the chain head), this receipt has a Previous hash: field containing the hash of receipt 1. That linkage is what makes the chain tamper-evident: altering receipt 1 changes its hash, invalidates receipt 2’s back-pointer, and verification catches it immediately.


StepWhat happened
EmitSDK sent an event to the daemon over a Unix socket; daemon signed and stored the receipt
ListCLI read the persisted chain from the SQLite store
VerifyCLI re-derived hashes, checked signatures, and confirmed chain linkage — all without the daemon being involved

The daemon is the only process that ever holds the signing key. The agent, the CLI, and any downstream auditor only ever see signed receipts and the public key.