Test mode & sandbox
Build and exercise a full checkout with zero real money — the offline mock rail, hosted test keys and mode isolation, the deterministic mock facilitator, and a sandbox loop that mirrors production exactly.
You can drive the entire checkout — discovery, the acceptance gate, settlement, and a signed receipt — without moving a cent. Two layers make this safe, and they compose: the offline mock rail for local testing, and hosted test keys with hard mode isolation for an end-to-end sandbox that mirrors production.
Test mode isn't a stripped-down path. The gate, the receipt, the webhooks, and the reason codes are identical to live — only the money is fake. That's the whole point: what passes in sandbox passes in production.
Layer 1 — the offline mock rail (self-hosted)
The mock rail makes the whole flow runnable with no chain, no network, and no install beyond the SDK. It does structural validation only and always "captures" successfully, so you can exercise the acceptance gate — caps, rate limits, mandate requirements, reputation, intent — deterministically in tests and demos.
import { createCheckout, STRICT } from '@veto-protocol/checkout';
const checkout = createCheckout({
merchant: { id: 'acme', name: 'Acme', domain: 'shop.acme.example' },
catalog: [{ sku: 'pro', name: 'Pro plan', price: { amount: '49.00', currency: 'USD' }, available: true }],
receiving: { mock: { account: 'acct_test' } },
policy: STRICT(), // requires a mandate for ANY spend
});
const created = await checkout.createSession({
agent_id: '11111111-1111-1111-1111-111111111111',
items: [{ sku: 'pro', qty: 1 }],
rail: 'mock',
});
const settled = await checkout.settle(created.body.session_id, { payment: { mock: true } });
console.log(settled.status); // 'rejected' — STRICT wants a mandate over $0
console.log(settled.body.reason_codes); // ['MANDATE_REQUIRED'] (and/or REPUTATION_TOO_LOW)rails defaults to [x402, mock] when you omit it, so mock is available out of the box —
pass rail: 'mock' when creating a session to use it. The
getting-started test-mode page has the full mock-rail
walkthrough.
The mock rail never moves money — never set it as a live receiving destination. On the
hosted plane, a mock receiving rail on a live merchant is rejected with
RECEIVING_MOCK_IN_LIVE.
Layer 2 — hosted test keys & mode isolation
On the hosted control plane, every API key is either test or live, and the mode is baked into the key:
| Key prefix | livemode | Settles through |
|---|---|---|
veto_test_… | false | the mock facilitator — a deterministic fake txHash |
veto_live_… | true | the real CDP facilitator — real USDC on Base mainnet |
The isolation is absolute: a test key can never be handed the real on-chain facilitator.
Any attempt is rejected with TEST_MODE_RAIL_FORBIDDEN. A test key creates
test merchants, reads test orders, and settles on the mock — it cannot reach a live rail by any
path.
curl -X POST https://api.veto-checkout.com/v1/merchants \
-H "Authorization: Bearer veto_test_…" -H "Content-Type: application/json" \
-d '{ "slug": "acme-sandbox", "name": "Acme (sandbox)", "domain": "shop.acme.example",
"receiving": { "x402": { "chain": "base-sepolia", "address": "0xYourAddr…", "asset": "USDC" } } }'The deterministic mock facilitator
A test-key settle still runs the real signature recovery (ecrecover) — so a test
exercises the genuine EIP-3009 crypto path and a SIGNER_MISMATCH in
sandbox is a real signing bug you've caught early. Only the broadcast is faked: the
facilitator returns a deterministic fake txHash derived from the nonce, so test receipts are
stable and replay-safe across runs. You get a complete, signed, verifiable order with no chain.
A sandbox loop that mirrors production
Build on a test key
Create your merchant, catalog, receiving (Base Sepolia), and policy with a veto_test_ key.
Everything is identical to live except the key.
Exercise every decision branch
Drive the gate to each terminal outcome and assert on the reason codes — they're the stable contract:
| Want | How |
|---|---|
| accept | a small spend under your requireMandateOverUsd, known agent |
| reject | a spend over a cap, or a blocked agent → OVER_PER_TX_CAP / AGENT_BLOCKED |
| hold | a tier below holdForReviewBelowTier → HOLD_FOR_REVIEW |
See policies & trust for the full rule list.
Verify webhooks against a test endpoint
Register a test webhook endpoint and confirm your handler verifies the signature and is
idempotent — the webhooks guide shows the exact verifier. Test
deliveries carry "livemode": false in the envelope.
Verify a sandbox receipt
A sandbox order ships a real, signed merchant receipt. Verify it offline — the signature path is identical to live; only the settlement txHash is the deterministic fake.
Promote with one change
When sandbox is green, the only change to go live is the key (and a Base-Sepolia →
base receiving flip). Walk the x402 + CDP cutover;
nothing about the gate, receipts, or webhooks changes.
Run the bundled example pair
The example pack ships a stub merchant and a buyer agent you can run offline with no install — the fastest way to see the whole loop:
npm run stub # node --experimental-strip-types examples/stub-merchant/server.ts
npm run buy # node --experimental-strip-types examples/buyer-agent/buy.tsThe buyer agent runs four scenarios end to end — a small no-mandate purchase (accepted), a
larger one (held), a mandate-backed purchase (accepted, premium tier), and a mandate replay
(rejected with MANDATE_REPLAY) — so you can watch every branch of the
gate without a chain.
Add agentic checkout to your site
The stack-agnostic way to make your store discoverable and buyable by AI agents — the discovery anchor, the manifest, and mounting the protocol on Node, Next.js, Express, or Hono (or any framework).
Go live with x402 + Coinbase CDP
Settle real USDC over HTTP-402 with a Coinbase CDP facilitator — from Base Sepolia testnet to Base mainnet, the EIP-712 domain trap, the txHash-is-authoritative rule, funding, and the live cutover checklist.