Every receipt K-Block issues is a portable evidence bundle: a .kbundle file with the signed receipt, every signed step it covers and the identity documents of every signer. You can verify it in three independent ways, and all three run the same checks:
- in the dashboard, entirely in your browser;
- in your own code, with the pre-release package
@kblockai/sdk(entry point@kblockai/sdk/verify); - on the command line, with the Rust
kblockCLI (not distributed in this release; see With the Rust CLI).
None of them takes the Cloud API's word for the result: the checks need only the bundle and the gateway KID you expect. The SDK and the CLI work offline; the dashboard fetches the bundle (and the gateway's identity documents) from the Cloud API, then checks it in your browser. Read What a receipt proves first if you have not, so you know what a valid result means.
Download the bundle
- Go to Receipts and open a receipt, or open an action and select Open and verify receipt.
- Select Download .kbundle.
The file is named after the receipt object, for example ko_….kbundle. The receipt page also shows the bundle's size and SHA-256, the hash recorded when the receipt was issued.
Agents get the receipt ID with the result of a call (_meta.kblock.receipt_id). The TypeScript client's receiptUrls() and the Python package's receipt_urls() turn it into the bundle URL; see TypeScript client and Python.
Verify in the browser
On a receipt's page, select Verify in this browser. The dashboard fetches the bundle, compares its SHA-256 with the value recorded at issuance, and runs the SDK verifier in your browser. The result appears under Independent check in this browser, with the full report in Verification in this browser below.
To verify a file you already have (for example one a customer sent you), go to Receipts, find Verify a receipt file, select Choose .kbundle file, then Verify file. Nothing is uploaded; files up to 16 MiB are accepted.
The browser's verdict also checks that the receipt was issued by the gateway identity the dashboard trusts (the pinned identity of the Gateway your agents connect to):
| Verdict | What to do |
|---|---|
| VALID | Nothing. The receipt verified and the pinned gateway issued it for what the page shows. |
| CONSISTENT | The dashboard could not establish which Gateway identity to trust. Compare the issuer KID with a Gateway KID you already trust (one K-Block gave you, or one you noted earlier when the dashboard showed it after you created a gateway API key). If the dashboard keeps showing this verdict, contact K-Block support. |
| NOT THIS GATEWAY | Another identity issued the receipt or signed its steps. Do not rely on it as a receipt of the Gateway your agents connect to. |
| MISMATCH | The bundle verifies but belongs to another receipt, thread, organization or action than the page shows. |
| INVALID | The bundle did not verify, or its hash differs from the recorded one. See What a tampered bundle looks like. |
To count attestations from K-Block-aware upstream servers, select them under Trust attestations from; the report updates at once. The list offers the K-Block-aware servers registered in your organization; when there are none, a line says so instead. Leave it empty if you do not want to trust any upstream.
With the TypeScript SDK
@kblockai/sdk/verify contains only the verifier. It needs Node.js 22 or later and makes no network requests. Install the pre-release version:
npm install --save-exact @kblockai/sdk@0.1.0-rc.1This program verifies a bundle, prints the same text report as the CLI, and checks that the Gateway your agents connect to issued it. It reads the Gateway KID it expects from KBLOCK_GATEWAY_KID, an environment variable of your own. Set it to the Gateway KID that K-Block gave you, or to the one the dashboard shows after you create a gateway API key.
import { readFile } from "node:fs/promises";
import { Kid, renderText, verifyBundleJson } from "@kblockai/sdk/verify";
const file = process.argv[2] ?? "receipt.kbundle";
// The Gateway KID you trust (from the dashboard or from K-Block), never from the bundle.
const gatewayKid = process.env.KBLOCK_GATEWAY_KID ?? "";
// Optional: KIDs of K-Block-aware upstream servers whose attestations you trust, comma-separated.
const trustedAttesters = (process.env.TRUSTED_ATTESTERS ?? "")
.split(",")
.filter((kid) => kid !== "")
.map((kid) => Kid.parse(kid));
// Never throws for a bad bundle: every problem is in the report.
const report = verifyBundleJson(await readFile(file, "utf8"), { trustedAttesters });
console.log(renderText(report));
// A valid report proves the bundle is consistent and correctly signed.
// Whether the Gateway you trust issued it is a separate check.
const fromYourGateway = gatewayKid !== "" && report.issuer === gatewayKid;
if (!fromYourGateway) console.log(`The issuer ${report.issuer ?? "(none)"} is not the expected gateway.`);
process.exit(report.valid && fromYourGateway ? 0 : 1);verifyBundleJson returns a ReceiptReport; report.toJSON() is the same JSON that the CLI prints with --json. Kid.parse throws for a malformed KID, so a typo in your configuration fails loudly. The Receipt verification SDK page describes every export.
The Python package kblock does not verify receipts in this version. Use the TypeScript SDK or the Rust CLI from Python projects.
With the Rust CLI
The kblock CLI, written in Rust, is the reference verifier. It is not distributed as a prebuilt binary or a package in this release, so verify in the dashboard or with the TypeScript SDK if you do not have it; they run the same checks. If you have the CLI, verify a bundle like this:
kblock verify receipt.kbundle
kblock verify receipt.kbundle --json
kblock verify receipt.kbundle --trust kid:kb:<upstream-server-kid>| Option | Effect |
|---|---|
- instead of a file | Read the bundle from standard input. |
--json | Print the machine-readable report instead of text. |
--trust <KID> | Trust attestations signed by this KID (repeatable). |
--identities <FILE> | Load current identity documents obtained outside the bundle (a JSON array of identity objects, or a single one), so a later key-compromise report is detected (repeatable). |
--workflow <PATH>[=sha256:<digest>] | Register a custom workflow definition; Cloud receipts use the built-in one and do not need it. |
The exit code is 0 for a valid receipt, 1 for an invalid one and 2 for an error before a verdict (an unreadable file, a malformed --trust KID or --identities file). The CLI does not compare the issuer with the Gateway KID you trust: check the Issued by: line yourself.
What a tampered bundle looks like
Change any byte of a signed step, for example the amount in the RESULT, and every verifier rejects the bundle: object IDs are hashes of the content, so the altered object no longer matches its ID. The CLI and renderText print a report like this one (an example; the object IDs are shortened here, and long lines end in …):
K-Block Receipt
Bundle format: VALID
Identity chains: SKIPPED
Signature chain: INVALID
↳ object ID ko_nlit…6q7a does not match its content (recomputed ko_wvmq…psaq)
Receipt coverage: SKIPPED
Graph integrity: SKIPPED
Workflow & authorization: SKIPPED
Receipt summary: SKIPPED
The interaction details are not shown because the receipt did not verify.
Errors:
ID_MISMATCH object ID ko_nlit…6q7a does not match its content (recomputed ko_wvmq…
Note: Values ending in … are shortened; the JSON report (--json) has them in full.
RESULT: INVALID
Note: K-Block proves who signed what, in which order, under which authority. It does not
prove that real-world effects occurred unless a trusted attester signed that claim.The CLI exits with 1, report.valid is false, and the dashboard shows INVALID. On a receipt's page, a downloaded bundle whose SHA-256 differs from the recorded value is also INVALID, with the notice Bundle hash mismatch.
Other tampering produces other codes, for example INVALID_SIGNATURE for an altered signature, RECEIPT_MISMATCH for a removed step, UNKNOWN_IDENTITY for a removed identity document and UNSUPPORTED_VERSION for an unknown bundle version.
Interpreting the report
A report has the same parts in the dashboard, in ReceiptReport and in the CLI's JSON:
| Part | Meaning |
|---|---|
valid / RESULT: | True only if every check is VALID and there are no errors. |
checks | The seven checks in order: Bundle format, Identity chains, Signature chain, Receipt coverage, Graph integrity, Workflow & authorization, Receipt summary. Each is VALID, INVALID or SKIPPED (not reached because an earlier check failed). |
issuer, issued_at | Who signed the receipt and when they say they did. Compare issuer with the Gateway KID you trust. |
outcome | succeeded or failed for a RESULT head; final for a rejection or cancellation; pending (PROPOSAL) or granted (APPROVAL) for a decision-only authorization receipt, which proves the authorization, not an execution. |
summary | Who requested, proposed, approved, rejected, cancelled and executed, on whose behalf, and the result_digest, re-derived by the verifier. An authorization receipt has no executor and no result. |
path | The signed steps from the TASK to the head, with signer and asserted time. |
actors | Each signer's KID, actor type and pinned identity document. |
attestations | Claims by upstream servers, with about_path (every subject is on the verified path) and trusted (you chose to trust the attester, and the attestation is valid and about the path). |
errors | Why the receipt is invalid, each with a code. |
warnings | Findings that do not invalidate it, such as KEY_LATER_COMPROMISED (the key was reported compromised after the step was signed) or IDENTITY_DOCUMENT_IGNORED. |
notes | What could not be established, for example "Key freshness not checked …" when no identity documents were supplied from outside the bundle. |
Every text report ends with the same reminder: K-Block proves who signed what, in which order and under which authority, not that real-world effects occurred unless a trusted attester signed that claim.