Receipt Authority API Quickstart
Exercise the complete synthetic flow in test mode without real money, a real provider, or a live credential.
1. Create a test application
Sign in with a verified Receipt email, accept the Terms of Service, and register exact redirect URIs. Your first secret is shown once.
RECEIPT_PARTNER_BASE_URL=https://receiptprotocol.com
RECEIPT_PARTNER_KEY=rpt_test_<shown-once>
RECEIPT_PARTNER_REFERENCE=pt_<opaque-reference>2. Install the server-side SDKs
Install both packages from npm. The checked-in example starts its own localhost callback and requires no staff or database work.
npm install @receiptprotocol/partner-sdk
npm install @receiptprotocol/gate-sdk
npm --prefix public/partner-gateway/example install
npm --prefix public/partner-gateway/example start3. Create a mandate intent
import { ReceiptPartnerClient } from "@receiptprotocol/partner-sdk";
const receipt = new ReceiptPartnerClient({
baseUrl: process.env.RECEIPT_PARTNER_BASE_URL!,
partnerKey: process.env.RECEIPT_PARTNER_KEY!,
});
const intent = await receipt.createMandateIntent({ /* synthetic terms */ });
// Redirect the principal to intent.hosted_review_url.
// Submit the exact synthetic action after mandate activation.4. Run every decision path
The example first runs the permitted, denied, and indeterminate/reconciliation paths. Then use the mandate centre to require fresh approval on the connected agent and rerun with RECEIPT_APPROVAL_READY=true. The example persists the exact synthetic action locally, opens the one-time hosted approval, and safely replays that same material action after approval.
- 1.Receive mandate activation
- 2.Submit one permitted synthetic action
- 3.Verify the signed policy decision locally
- 4.Claim the one-time admission in the mock credential-owning executor
- 5.Report the mock provider result
- 6.Submit one denied action
- 7.Submit one approval-required action
- 8.Reconcile one synthetic indeterminate result
cURL
curl "$RECEIPT_PARTNER_BASE_URL/api/partner/v1/mandate-intents" \
-H "X-Receipt-Partner-Key: $RECEIPT_PARTNER_KEY" \
-H "Content-Type: application/json" \
--data @synthetic-mandate-intent.json
# After the principal authorizes the hosted intent:
curl "$RECEIPT_PARTNER_BASE_URL/api/partner/v1/proposals" \
-H "X-Receipt-Partner-Key: $RECEIPT_PARTNER_KEY" \
-H "Content-Type: application/json" \
--data @synthetic-proposal.json
curl "$RECEIPT_PARTNER_BASE_URL/api/partner/v1/signing-keys" \
-H "X-Receipt-Partner-Key: $RECEIPT_PARTNER_KEY"Expected output contains test-mode verification keys and an explicit non-production issuer label. Never commit the key or place it in browser code.
Framework recipes
Keep the partner key and webhook signing secret in server-only environment variables in every framework. Browser callback pages should send results to your backend for verification before showing success.
Vite + Supabase
Add /receipt/callback to the Vite router. Put Receipt client calls, callback-result verification, and webhook verification in Supabase Edge Functions; never expose the partner key through a VITE_* variable.
Next.js App Router
Use app/receipt/callback/page.tsx for the return screen and server route handlers for result verification and webhooks. In the webhook handler, captureawait request.text() once, parse it once with JSON.parse, and pass that parsed object as payload to verifyPartnerWebhookwith the signing secret, endpoint URL, and Receipt headers.
Express
Create a browser callback route plus server-only verification route. Mount a raw text or buffer body reader on the Receipt webhook route before express.json(), parse the captured value once, then pass that object as payload to the SDK verifier with the signing secret, endpoint URL, and Receipt headers.
Cloudflare Workers
Store secrets as Worker secrets, not public bindings. Captureawait request.text() once, parse it once, and pass the object aspayload with the signing secret, endpoint URL, and Receipt headers. Return 2xx quickly and move slower processing behind ctx.waitUntil().
Common errors
partner_binding_mismatch- The key, application reference, or action-profile grant does not match.
superseded- Reload the current mandate version before proposing the action.
sandbox_proposal_evaluations_quota_exceeded- The UTC-day test-mode proposal limit is exhausted.
idempotency_conflict- Reuse an idempotency key only with the identical canonical request.
