Skip to content
Beta — Live system under active development. Expect occasional hiccups.

Verify a Receipt

Every Vertical Marketplace API response is wrapped in a provenance envelope with an Ed25519-signed receipt. Paste any response below to check its signature against the platform's public key — no trust in us required.

The receipt signs the message keyId.signedAt.digest, where digest = sha256 of the response body with the provenance block removed. We verify the signature and, when the payload is present, re-compute the digest to confirm the body hasn't been altered.

Platform public key

AlgorithmEd25519
Key IDvm-ed25519-9b79937f55d7
Public key (raw base64url)
9pKT9Rj1oXSNtzex3_bPXkSEILSKB6DtVvSfncOpGFU

Fetch it programmatically at GET /api/signing-key, or verify server-side by POSTing to /api/verify-receipt.

Verify offline — without trusting us

A receipt that only verifies against our server is a log entry. Save the public key once, then check any VM Pay receipt on your own machine — works even if verticalmarketplace.ai is unreachable:

// node verify.js  — no dependencies beyond Node 18+
const crypto = require("node:crypto");
const receipt = require("./receipt.json");        // GET /api/vmpay/receipts/:sessionId
const publicKeyPem = require("./key.json").publicKeyPem; // GET /api/vmpay/signing-key (save once)

// Canonical JSON: keys sorted at every level, so key order never matters.
const stable = (v) =>
  v === null || typeof v !== "object" ? JSON.stringify(v)
  : Array.isArray(v) ? `[${v.map(stable).join(",")}]`
  : `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${stable(v[k])}`).join(",")}}`;

const { signature, provenance, ...body } = receipt;
const message = Buffer.concat([
  Buffer.from(`${signature.keyId}.${signature.signedAt}.`),
  Buffer.from(stable(body)),
]);
const ok = crypto.verify(null, message, crypto.createPublicKey(publicKeyPem),
  Buffer.from(signature.signature, "base64url"));
console.log(ok ? "RECEIPT VERIFIED" : "TAMPERED OR FORGED");

Marketplace provenance envelopes verify the same way over keyId.signedAt.digest — see /api/signing-key for that key.