Verifying a receipt
In the browser
The fastest way is the public verifier at
app.cloakapi.io/receipt-verifier.
Paste a receipt JSON, press Verify. No login required. The page calls
the public POST /api/v1/receipts/verify endpoint and renders a per-step
breakdown.
From the CLI
POST /api/v1/receipts/verify accepts three payload shapes. All three
return the same response schema.
Shape A — envelope directly (recommended)
Pass the receipt envelope as the root JSON object:
curl -X POST https://api.cloakapi.io/api/v1/receipts/verify \ -H 'Content-Type: application/json' \ -d @receipt.jsonShape B — wrapped in receipt key
curl -X POST https://api.cloakapi.io/api/v1/receipts/verify \ -H 'Content-Type: application/json' \ -d "{\"receipt\": $(cat receipt.json)}"Shape C — JTI lookup (campaign / prerender fixtures)
If you only have the receipt JTI (from X-CloakAPI-Receipt-V2-JTI header
or GET /api/v1/receipts), pass it as {"jti": "..."}:
curl -X POST https://api.cloakapi.io/api/v1/receipts/verify \ -H 'Content-Type: application/json' \ -d '{"jti": "01HQABCD1234EFGH56789012"}'The server resolves the envelope from the receipt chain and returns the same verification response.
Response
{ "valid": true, "reason": null, "checks": [ {"label": "Schema valid (OpenReceipt)", "status": "ok", "detail": "0.4 ms"}, {"label": "Algorithm allow-listed", "status": "ok", "detail": "ES256"}, {"label": "Freshness (iat / nbf / exp)", "status": "ok", "detail": "iat=1748000000 within window"}, {"label": "Signature (ES256 / ecdsa-p256-sha256)", "status": "ok", "detail": "8.1 ms"}, {"label": "Chain link present", "status": "ok", "detail": "seq 47"} ]}200 means valid; 422 with valid: false and a per-step checks array
means at least one check failed — the checks array tells you which step
and why.
If the payload is not one of the three recognised shapes, the API returns
400 with {"error": {"code": "invalid_payload", "message": "..."}}.
Decoding the X-CloakAPI-Receipt-V2 header
The X-CloakAPI-Receipt-V2 response header is a base64-encoded JSON blob.
Decode it to get the envelope before posting to verify:
HEADER_VALUE="<paste base64 from response header here>"echo "$HEADER_VALUE" | base64 -d > receipt.jsoncurl -X POST https://api.cloakapi.io/api/v1/receipts/verify \ -H 'Content-Type: application/json' \ -d @receipt.jsonIn your own code
A receipt is just a signed JSON object. Any standard ECDSA-P-256-SHA-256 verifier works — the gateway is just a convenience wrapper.
Pseudocode (gateway-signed v2 envelope):
jwks = HTTP GET https://api.cloakapi.io/api/.well-known/cloakapi-receipt-pubkeys.jwkskey = jwks.keys.find(k => k.kid == receipt.kid)canon = json_canonicalize(receipt without "signature")ok = ecdsa_verify(key, sha256(canon), receipt.signature)Client-signed v3 envelopes are self-contained: the attester public key is
embedded in claims["v3-attestation"].attester_public_key, so verification
works fully offline with no JWKS fetch — canonicalise the envelope minus
sig, SHA-256, verify against the embedded key. See the
v3 specification §8.2.
Published reference implementations (Rust — FIPS-compliant via aws-lc-rs —
plus TypeScript and Python) are at
signedreceipts.org/source.