Every decision Decionis governs produces a Decision Dossier: a signed record of what was proposed, which policy version judged it, and what happened next. It is signed with Ed25519 against a public key we publish, so you can check it without an account — and without taking our word for the result.
Paste the whole verification link. If you only have the dossier id, you will also need the sig token that came with it — a dossier cannot be opened from its id alone.
The dossier page reports four checks and states each one plainly, including when a check fails. A failing signature is shown as a failure — it is never rounded up to “unverified”.
The Ed25519 signature over each signed document verifies against the public key published in our JWKS. This is the check that makes the record ours and unaltered.
The dossier names the policy version that governed the action, along with hashes of the rules and parameters in force at evaluation time — so the policy can be pinned, not just described.
The dossier's entry hashes into the decision ledger, and that chain is intact. A record cannot be quietly removed or reordered after the fact.
Each signed artifact's canonical SHA-256 digest matches the document it covers. A bundle that is present but fails this check is reported as failed, not as unverified.
A verification page run by the vendor being verified is worth exactly as much as the method behind it. So here is the method. Every input is public, and the check runs on your machine against a key you fetch yourself.
Ed25519 (EdDSA)
One signature per signed document, over its canonical bytes.
sort keys → JSON.stringify
Object keys sorted recursively; arrays and scalars left in place. Deliberately boring, so it is reimplementable in any language.
Published as a JWKS. Rotation keeps superseded keys listed through a grace window, so an older dossier stays verifiable after a rotation.
https://api.decionis.com/v1/.well-known/decision-dossier-jwks.jsonThe dossier page links its public proof bundle. Hand that URL to the open verifier and it fetches the bundle, discovers the JWKS from it, and runs every check locally.
npx @decionis/verify "<proof-bundle URL from the dossier page>"0 verified, 1 not verified, 2 a usage or IO error — so it drops into CI as a gate.--json for machine-readable output, or --file dossier.json --jwks https://api.decionis.com/v1/.well-known/decision-dossier-jwks.json to check a dossier you saved earlier.Nothing above is privileged. If you would rather not run our code at all, these are the four steps the verifier performs, in any language:
Fetch the dossier's public proof bundle — the dossier page links it, and the response carries the signed documents plus the absolute JWKS URL.
Fetch that JWKS and pick the Ed25519 key whose kid matches the bundle's key id.
For each artifact in the bundle, canonicalise the document it points at, and check its SHA-256 against the digest recorded in the bundle.
Verify the artifact's Ed25519 signature over those same canonical bytes. All artifacts must pass.
// verify-dossier.mjs — Node 18+, no dependencies.
// Usage: node verify-dossier.mjs "<proof-bundle URL from the dossier page>"
import { createHash, createPublicKey, verify } from "node:crypto";
const { dossier_payload, jwks_url } = await (await fetch(process.argv[2])).json();
const { keys } = await (await fetch(jwks_url)).json();
// Canonical JSON: sort object keys recursively, leave arrays and scalars alone.
const canonical = (value) =>
Array.isArray(value)
? value.map(canonical)
: value && typeof value === "object"
? Object.fromEntries(Object.keys(value).sort().map((k) => [k, canonical(value[k])]))
: value;
const bundle = dossier_payload.integrity.proof_bundle;
const jwk = keys.find((k) => k.kty === "OKP" && k.crv === "Ed25519" && k.kid === bundle.key_id);
const publicKey = createPublicKey({ key: jwk, format: "jwk" });
for (const artifact of bundle.artifacts) {
const document = artifact.document_path
.split("/")
.slice(1)
.reduce((node, segment) => node?.[segment], dossier_payload);
const bytes = Buffer.from(JSON.stringify(canonical(document)), "utf8");
const digest = createHash("sha256").update(bytes).digest("hex");
console.log(artifact.document_path, {
sha256: digest === artifact.canonical_document_sha256,
signature: verify(null, bytes, publicKey, Buffer.from(artifact.signature, "base64url")),
});
}The same checks the command above runs, written out in full: Node’s built-in crypto, no Decionis package, SDK, or account.
Verification answers one question well and several others not at all. The distinctions matter most to the people who rely on them hardest.
The fields, the integrity block, and what each signed document contains.
Produce one from an approval or webhook payload and follow it through to a verifiable record.
Where authority is separated from execution, and what is retained.
The exact certification status, stated without rounding up.