Docs

Receipt verification

Find out why a receipt does not verify: read the report, compare the issuer with your Gateway KID, and recognise changed, incomplete or unsupported bundles.

A receipt is verified from its .kbundle file alone: in the dashboard (in your browser), with @kblockai/sdk/verify, or with the Rust kblock CLI. All three run the same seven checks and produce the same report. This page helps when the result is not the one you expect: an INVALID or NOT THIS GATEWAY verdict, a report with errors, or a bundle the Cloud API cannot serve.

Start with Read the report: the first failing check names the cause. Then go to the matching section below. To learn how to run a verification in the first place, see Verify a receipt.

Find your symptom

What you seeGo to
NOT THIS GATEWAY, or an issuer that is not your gateway's KIDThe receipt is not from your gateway
CONSISTENT in the dashboardNo gateway KID is pinned
Bundle hash mismatch, ID_MISMATCH, INVALID_SIGNATUREThe bundle was changed
UNSUPPORTED_VERSION, MALFORMED_JSON or another Bundle format errorThe file is not a bundle this verifier reads
RECEIPT_MISMATCH "covered objects differ", UNKNOWN_IDENTITYAn object is missing from the bundle
"Could not load the receipt" or "Could not fetch the bundle" with "K-Block could not complete this request because of a problem on its side." or INVALID_STATEThe Cloud API cannot serve the bundle
MISMATCH on a receipt pageMISMATCH on a receipt page
Verification by the Cloud and the browser check disagreeThe two dashboard verdicts disagree
A warning or a note on a receipt that is VALIDWarnings and notes on a valid receipt
The CLI ends with error: … and exit code 2Command-line errors

Read the report

Every verifier reports seven checks, in this order: Bundle format, Identity chains, Signature chain, Receipt coverage, Graph integrity, Workflow & authorization, Receipt summary. Each one is VALID, INVALID or SKIPPED. A receipt is valid only when all seven are VALID and there are no errors.

Read it with three rules:

  1. The first INVALID check is the cause. Later checks build on earlier ones, so a failure often stops verification. SKIPPED means "not reached", not "passed" and not a second failure. A SKIPPED check can therefore appear above the failing one: an ID_MISMATCH found while the file is read leaves Identity chains SKIPPED above the failing Signature chain, and Signature chain is completed only after Receipt coverage, so a coverage failure leaves it SKIPPED too.
  2. Each error has a code, usually the object it concerns, and a message. The code is stable and is what the sections below use. The message names the details, such as the object IDs involved.
  3. The text report shortens long values. The CLI's text output and renderText cut check details, messages and other text taken from the bundle with … so that they end before column 80, and say so in a note. For the full values, use the CLI's --json or report.toJSON() in the SDK.

In the dashboard, the same report is the Verification in this browser panel: the Checks list ("N of 7 passed"), the Errors and Warnings notices, and What this verification cannot establish for the notes.

CheckFails whenTypical codes
Bundle formatThe file is not JSON, not a K-Block bundle, a version this verifier does not support, too large, or an object in it is malformed.MALFORMED_JSON, INVALID_BUNDLE, INVALID_ENVELOPE, UNSUPPORTED_VERSION, TOO_LARGE
Identity chainsThe identity documents in the bundle do not form valid identity chains.INVALID_IDENTITY, IDENTITY_EQUIVOCATION
Signature chainAn object no longer matches its ID (its content changed), a signature is wrong, or a signer's key is unknown or revoked.ID_MISMATCH, INVALID_SIGNATURE, UNKNOWN_KEY, KEY_REVOKED, UNKNOWN_IDENTITY
Receipt coverageThe bundle's objects or identity documents are not exactly the ones the receipt commits to.RECEIPT_MISMATCH, UNKNOWN_IDENTITY
Graph integrityReferences between the signed steps are missing or broken, or the path does not lead to the receipt's root.MISSING_REFERENCE, INCOMPLETE_GRAPH, RECEIPT_MISMATCH
Workflow & authorizationA step is not allowed by the workflow, or no valid delegation covers it.INVALID_TRANSITION, UNAUTHORIZED_ACTOR, DELEGATION_REVOKED, CONSTRAINT_VIOLATION
Receipt summaryThe summary or outcome the issuer signed differs from the one the verifier derives from the signed steps.RECEIPT_MISMATCH

The verifier never throws for a bad bundle: every problem is in the report. This program prints the cause, the findings and the issuer of a bundle. It compares the issuer with the Gateway KID in KBLOCK_GATEWAY_KID, an environment variable of your own that holds the Gateway KID K-Block gives you:

diagnose-receipt.ts
import { readFile } from "node:fs/promises";
import { verifyBundleJson } from "@kblockai/sdk/verify";

const file = process.argv[2] ?? "receipt.kbundle";
// The Gateway KID, the identity K-Block's Gateway signs with: the dashboard shows it after
// you create a gateway API key; if it does not, contact K-Block support.
const gatewayKid = (process.env.KBLOCK_GATEWAY_KID ?? "").trim();

const report = verifyBundleJson(await readFile(file, "utf8"));

// The first INVALID check is the cause; SKIPPED checks were not reached.
const cause = report.checks.find((check) => check.status === "INVALID");
console.log(cause === undefined ? "All checks passed." : `First failing check: ${cause.name}`);
for (const e of report.errors) console.log(`error ${e.code} ${e.object ?? ""}: ${e.message}`);
for (const w of report.warnings) console.log(`warning ${w.code}: ${w.message}`);
for (const n of report.notes) console.log(`note: ${n}`);

// A valid report does not say who issued the receipt: compare the issuer yourself.
console.log(`issuer: ${report.issuer ?? "(not read)"}`);
const fromYourGateway = report.valid && gatewayKid !== "" && report.issuer === gatewayKid;
if (report.valid && !fromYourGateway) console.log("The bundle verifies, but not as a receipt of your gateway.");
process.exit(fromYourGateway ? 0 : 1);

See Verify a receipt for every part of the report, and Receipt fields for the fields of the receipt itself.

The receipt is not from your gateway

A valid report proves that a bundle is internally consistent and correctly signed by the identities it contains. Anyone can build such a bundle with their own keys. Whether the K-Block Gateway issued it is a separate check: the receipt's issuer must be your Gateway KID. Every K-Block Cloud receipt is issued under the gateway identity, also the receipts the Cloud API issues for rejections, cancellations and recovered executions.

Symptoms.

  • The dashboard shows NOT THIS GATEWAY and the notice Not issued by this deployment's gateway, for example "The receipt was issued by kid:kb:…, not by this deployment's gateway kid:kb:… …". The Issuer row is marked "not this deployment's gateway".
  • The notice can also name a step instead: the dashboard requires the pinned gateway to have signed the receipt's task, proposal, execution and result too.
  • In your own code or with the CLI, the report is valid but report.issuer (the Issued by: line) is not the KID you expect.

Causes and fixes.

CauseHow to tellFix
The dashboard pins the wrong Gateway KIDEvery receipt, including new ones, shows NOT THIS GATEWAY with the same issuer. Signing a delegation fails with a message that begins "The Cloud API reports the gateway identity …, but this dashboard trusts …" and says "Nothing was signed." Approval requests show the same message under "The signed evidence could not be verified — do not decide this request", and cannot be decided.Do not rely on the browser verdict until this is fixed. Contact K-Block support and give them the issuer KID the notice shows. Meanwhile, verify with the SDK or the CLI against the Gateway KID that K-Block gave you.
K-Block replaced the Gateway identityReceipts issued before the change show NOT THIS GATEWAY; receipts issued after it are VALID.Expected: the dashboard compares every receipt with the one pinned KID. Verify older receipts with the SDK or the CLI against the KID that was in use when they were issued.
The bundle was not issued by K-Block CloudUsually a file checked under Verify a receipt file. The Organization (signed) row can show "another organization".Do not rely on it as a K-Block Cloud receipt. If you know who issued it, verify it against that issuer's KID.
Your own check compares with the wrong valueThe SDK or CLI report is valid, but your comparison fails.Compare with the exact KID, kid:kb: prefix included and without surrounding whitespace or a trailing newline.

Finding the right KID. The dashboard shows the Gateway KID right after you create a gateway API key on the API keys page ("The Gateway signs receipts as this identity"); if it does not show it, contact K-Block support. The KID on the API keys page is the one the dashboard pins, so it is wrong too when the pin is wrong: when it disagrees with the Gateway KID that K-Block gave you, trust the one K-Block gave you and contact K-Block support. GET /api/v1/gateway/identity on the Cloud API returns the currently registered kid and its identity documents, but that answer comes from the same service whose receipts you are checking, so prefer a value you recorded yourself. Nothing warns about a wrong pin in advance: it shows only in the browser, on receipt pages, in the form that signs a delegation and on approval requests. See Identities and KIDs.

A caution such as "The task was requested on behalf of …, which is not this organization's current principal (…); it may be a previous principal" is not a failure. It appears on receipts issued before the organization's principal was replaced, and the verdict stays VALID.

No gateway KID is pinned

CONSISTENT means every check passed against the identity documents inside the bundle, but the dashboard has not been told which Gateway identity to trust, so it cannot tell whether your gateway issued the receipt. The Issuer row is marked "gateway not pinned".

Compare the issuer KID with the Gateway KID that K-Block gave you, or verify the bundle with the SDK and compare its issuer with that KID, as in the program above. The dashboard normally has a pinned Gateway identity, so if it keeps showing CONSISTENT, contact K-Block support. Once the dashboard has a pinned Gateway identity, it shows VALID or NOT THIS GATEWAY instead.

The bundle was changed

Every K-Object's ID is a hash of its content, and every object is signed. Changing any signed byte is therefore detected. The verifier's test vectors show what each kind of change produces:

ChangeCodeCheck that fails
The content of a signed step, for example the amount in the RESULT, a reference, or the receipt's own summaryID_MISMATCH "object ID … does not match its content (recomputed …)"Signature chain. Identity chains shows SKIPPED because the error stops verification while the file is read.
A signatureINVALID_SIGNATURE, then MISSING_REFERENCE for the step that refers to the broken one and RECEIPT_MISMATCH for the summarySignature chain, Graph integrity, Receipt summary
A receipt re-signed by the issuer with a false summaryRECEIPT_MISMATCH "issuer's summary differs from the verified graph in: …", followed by the differing fieldsReceipt summary

On a receipt's page, the dashboard also compares the SHA-256 of the bundle it downloaded with the hash recorded at issuance. A difference makes the verdict INVALID with the notice Bundle hash mismatch: "The bundle's SHA-256 differs from the hash recorded when the receipt was issued."

Two changes are not tampering, but still change the file:

  • Reformatting. Pretty-printing the JSON or changing its line endings keeps the bundle verifiable, because objects are hashed in their canonical form. The file's SHA-256 changes, though, so it no longer matches the SHA-256 on the receipt page or the x-kblock-bundle-sha256 header of the download.
  • A byte order mark. If a tool saved the file with a UTF-8 byte order mark at the start, verifyBundleJson refuses it with MALFORMED_JSON "expected value at position 0" (the program above reads the file with Node.js, which keeps the mark). Some Windows tools add one when they save a text file. Remove the mark, or download the bundle again.

Fix. Download the bundle again with Download .kbundle on the receipt page, or from GET /api/v1/orgs/<org-id>/receipts/<receipt-id>/bundle, save the bytes unchanged, and verify the fresh copy. Compare its SHA-256 with the value on the receipt page. If the fresh copy verifies, the other copy was changed after it was downloaded. If it does not, contact K-Block support with the receipt ID and the report.

The file is not a bundle this verifier reads

A Bundle format failure stops verification: the checks after it show SKIPPED.

CodeExample messageCause
UNSUPPORTED_VERSION"unsupported bundle version '9.9'"The file's kblock_bundle member is not "0.1".
UNSUPPORTED_VERSION"unsupported protocol version '0.2'"An object in the bundle has a protocol version other than "0.1".
MALFORMED_JSON"expected value at position 0", "EOF while parsing a value"The file is not JSON, is empty or cut short, or starts with a byte order mark.
DUPLICATE_KEY, INVALID_NUMBER—JSON that K-Block's strict parser refuses: a member name that appears twice, or a number it does not allow, such as a fraction.
INVALID_BUNDLE"malformed bundle: …"A required top-level member is missing, an unknown one was added, or the receipt member does not hold a receipt.
INVALID_ENVELOPE—An object in the bundle does not have the structure of a K-Block object.
TOO_LARGE—More than 1,024 objects, 1,024 identity documents or 1,024 disclosures.

This release reads bundle format 0.1 with Protocol v0.1 objects only, and it is the only format K-Block Cloud issues. A different version means the file was edited or was not produced by this K-Block release. See Limits and supported versions.

Fix. Look at the first members of the file: a K-Block bundle starts with "kblock_bundle": "0.1" and has the members receipt, objects and identities. Download the bundle again if you are not sure the copy is intact.

In the dashboard, Verify a receipt file accepts files up to 16 MiB. A larger file is refused before verification with "The file is larger than 16384 KiB."

An object is missing from the bundle

A receipt commits to an exact set of objects: the signed steps on the path from the TASK to the receipt's head, the delegations behind them and their revocations, attestations about those steps, and the identity document of every signer. The verifier compares the bundle with that commitment.

What is missing or extraWhat you see
A covered object, for example the APPROVALReceipt coverage INVALID with RECEIPT_MISMATCH "covered objects differ (missing: […], uncovered: [])". Signature chain and the later checks show SKIPPED.
An object the receipt does not coverThe same check: RECEIPT_MISMATCH with the extra IDs under uncovered, or "bundle covers objects unrelated to the head's path".
The identity documents of a signerUNKNOWN_IDENTITY "no identity documents for pinned kid:kb:…" under Receipt coverage. When the issuer's own documents are missing, the receipt's signature cannot be checked and Signature chain fails instead.
A pinned identity document replaced by anotherRECEIPT_MISMATCH "…: receipt pins identity document …, bundle's latest is …" under Receipt coverage.

For example, an excerpt of the text report of a bundle whose APPROVAL was removed (the removed_approval case of the verifier's test vectors):

Bundle format:            VALID
Identity chains:          VALID
Signature chain:          SKIPPED
Receipt coverage:         INVALID
    ↳ covered objects differ (missing: ["ko_xvrzn2u4xjn335vs2qlml4pfnrgygngyy…
Graph integrity:          SKIPPED
Workflow & authorization: SKIPPED
Receipt summary:          SKIPPED

The interaction details are not shown because the receipt did not verify.

Errors:
  RECEIPT_MISMATCH [ko_jer2b6…ng7q] covered objects differ (missing: ["ko_xvr…
Note: Values ending in … are shortened; the JSON report (--json) has them in full.

RESULT: INVALID

Cause. The file was put together or edited by hand, cut short, or merged from several bundles. A bundle downloaded unchanged from K-Block Cloud is complete.

Fix. Download the bundle again and verify the downloaded file without changing it.

The Cloud API cannot serve the bundle

Every time a receipt page loads and every time a bundle is downloaded, K-Block Cloud reads the stored bundle and checks it against the SHA-256 recorded at issuance. It never serves a bundle that fails this check. Because the receipt page is built from the bundle, the whole page fails with "Could not load the receipt"; a failure of Verify in this browser or Download .kbundle shows "Could not fetch the bundle".

When K-Block cannot read the stored receipt file (the file is missing, or K-Block's receipt storage failed), or the file no longer matches its recorded hash (it was changed or damaged, so K-Block refuses to serve it), the error says "K-Block could not complete this request because of a problem on its side." It goes on "If this keeps happening, contact K-Block support and give them the request ID below." and shows the Request ID. The Cloud API answers HTTP 500.

One case shows a code:

CodeHTTPMessageCause
INVALID_STATE409"the bundle is stored in the '…' backend, not '…'"A problem on K-Block's side: K-Block cannot currently read this receipt's file from where it was stored.

Fix. Nothing in the dashboard, the SDK or the CLI repairs a stored bundle. Reload the page later; if the error continues, contact K-Block support and give them the receipt ID and the request ID shown in the error (and the code, for INVALID_STATE). Tell them also whether every receipt fails, or only the receipts of executed calls while those of rejections and cancellations still load: that difference helps K-Block find the cause.

MISMATCH on a receipt page

MISMATCH means the downloaded bundle verifies, but its receipt, thread, organization or action is not the one the page shows. The notice The bundle does not match this receipt lists each difference, for example "The verified organization (…) does not match the organization this page shows (…)."

This should not happen with a bundle the Cloud API serves for the page. Do not rely on the bundle as this receipt, and contact K-Block support with the receipt ID and the notice.

The two dashboard verdicts disagree

A receipt page shows two verdicts side by side. They answer different questions:

Verification by the CloudIndependent check in this browser
RunsOn the Cloud API, on the stored bundleIn your browser, on the downloaded bundle
Gateway pinNot appliedApplied: VALID only for the pinned gateway
Identity documentsThe bundle's, plus those K-Block stores for every signerThe bundle's, plus the pinned gateway's current documents
Attesters trustedEvery K-Block-aware MCP server registered in the organizationOnly those you select under Trust attestations from
Also fails whenA signer's identity has equivocated (published two conflicting identity documents)The bundle's SHA-256 differs from the recorded hash
  • Cloud valid, browser CONSISTENT or NOT THIS GATEWAY. Expected when the pin is missing or wrong: only the browser applies it. See The receipt is not from your gateway.
  • Cloud invalid, browser VALID. The Cloud API knows identity documents the bundle does not contain, for example a later key-compromise report or an equivocation. Open Raw reports, then Cloud verification report: its errors and equivocated_identities say why. Do not rely on the receipt until K-Block support has explained it: contact them with the receipt ID and the Cloud verification report.

Report recorded at issuance, also under Raw reports, is the verification made when the receipt was issued, for comparison.

Warnings and notes on a valid receipt

Warnings and notes do not make a receipt invalid. They tell you what the verification could not establish.

FindingMeaningWhat to do
Note "Key freshness not checked for N of M signers…"The signers' identity documents came only from the bundle, so a key-compromise report published after issuance would not be seen. The dashboard supplies the pinned gateway's current documents, so it usually reports the other signers only.Pass current identity documents obtained outside the bundle: --identities <file> in the CLI, knownIdentities in the SDK.
KEY_LATER_COMPROMISEDA signer's key was reported compromised after the time the step claims. Times are asserted by the signer, not by a trusted clock.Treat the step with caution and ask the signer when the key was compromised. A key reported compromised before the step's time fails the receipt with KEY_REVOKED.
IDENTITY_UPDATED_SINCE_RECEIPTA signer has published a newer identity document since the receipt pinned its document; the objects were checked against the newer one.Usually nothing, for example after a key rotation.
IDENTITY_DOCUMENT_IGNOREDAn identity document you supplied is not part of the signer's authenticated chain and was ignored.Check where the document came from.
Attestation "not trusted by this verifier"The upstream server's attestation verified, but you did not choose to trust its attester.Select the server under Trust attestations from, or pass its KID with --trust or trustedAttesters, if you trust it.

The text report (the CLI's and renderText's) also ends with the same reminder every time: 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. That is a limit of every receipt, not a finding about this one.

Command-line errors

The kblock verify command exits with 0 for a valid receipt, 1 for an invalid one and 2 when it cannot reach a verdict. Exit code 2 comes with a line error: … on standard error, for example when:

  • the bundle file cannot be read;
  • a --trust value is not a valid KID (kid:kb: followed by the base32 digest);
  • an --identities file cannot be read, is not strict JSON, or holds something other than a K-Block object or an array of them;
  • a --workflow definition cannot be read or registered.

The CLI does not compare the issuer with your gateway: check the Issued by: line yourself. See Verify a receipt for how to run it and its options.

Next steps