Verification
Check a receipt's signature against the published signer, in code or through the API.
Prove / verification
You need the base64url receipt, its signature and a signer address you trust. Fetch the address once and pin it in your config.
curl https://YOUR-PROVN-HOST/v1/signer
# {"address":"0x...","scheme":"secp256k1/EIP-191"}Verify offline with viem
import { isAddressEqual, recoverMessageAddress, type Address, type Hex } from "viem";
// Pinned from GET /v1/signer
const PROVN_SIGNER: Address = "0x...";
export async function verifyReceipt(receipt: string, signature: Hex) {
const payload = Buffer.from(receipt, "base64url").toString("utf8");
const recovered = await recoverMessageAddress({ message: payload, signature });
return {
valid: isAddressEqual(recovered, PROVN_SIGNER),
recovered,
payload: JSON.parse(payload),
};
}The function decodes the receipt to its JSON string, recovers the address that signed that string under EIP-191 and compares it with your pinned address. In a browser, swap - for + and _ for /, decode with atob, and pass the bytes through TextDecoder.
Verify through the API
curl https://YOUR-PROVN-HOST/v1/receipts/verify \
-H "Content-Type: application/json" \
-d '{ "receipt": "eyJ...", "signature": "0x..." }'{
"valid": true,
"recovered": "0x...",
"signer": "0x...",
"matches": true,
"payload": { "v": 1, "kind": "inference", "cost_micro_usd": 21 }
}recovered is the address the signature resolves to, signer is the gateway's current signer, and matches compares them. This check asks Provn to vouch for its own receipt, so run the offline check when the result matters to someone else.
In the browser
/verify performs the same recovery in your browser. Paste the receipt and signature, then compare the recovered address with the one you pinned.
Match a fingerprint
For a sandbox or Terminal receipt, hash the exact code you sent and compare the first 16 hex characters with content_fingerprint. For inference, Provn hashes the JSON request body with its keys sorted.
printf '%s' "$CODE" | shasum -a 256 | cut -c1-16PROVN_SIGNER_KEY signs with an ephemeral development key. Receipts from that key stop matching once the operator sets a fixed one, so pin the signer after that happens.