Skip to main content
LearnGuidesTest agent payments without real money
GUIDE

Test agent payments without real money.

12 minutes
SHORT ANSWER

Swap your API key for a sk_test_ key and point the SDK at Base Sepolia. Mint test USDC with sandbox.fundAgent, drive payments deterministically with sandbox.simulatePayment, and tunnel your local webhook through ngrok. The sandbox mirrors live's response shapes so a flow that passes in test reliably passes in production - exercise the failure paths, not just the happy one.

PREREQUISITES

Before you start.

  • A working integration on live (or at least live-shaped) - see add-payments-to-agent.
  • A sk_test_ API key and matching test signing secret from the dashboard.
  • ngrok (or any HTTPS tunnel) for development-time webhook delivery.
  • A separate development environment - distinct env vars, distinct database (or at least distinct tables), distinct webhook URL.
  • Comfort with the webhook patterns guide - this guide assumes you have a handler to test against.
STEP 1 OF 5

Switch to test keys and Base Sepolia.

The sandbox is a parallel environment with its own API keys, its own signing secrets, and its own chain (Base Sepolia). The SDK auto-detects the network from the key prefix, so usually all you need to change is the env vars.

# .env.development
BLOCKCHAIN0X_API_KEY=sk_test_01J9...
BLOCKCHAIN0X_SIGNING_SECRET=whsec_test_01J9...
BLOCKCHAIN0X_NETWORK=base-sepolia
STEP 2 OF 5

Mint test USDC into the agent's wallet.

Test USDC has no monetary value but otherwise behaves identically to live USDC: same response shapes, same gas-fee modeling, same balance tracking. The sandbox lets you fund the agent's wallet with as much test USDC as you want, free.

TypeScript
import { Blockchain0x } from "@blockchain0x/sdk";

const client = new Blockchain0x({ apiKey: process.env.BLOCKCHAIN0X_API_KEY! });

// Mint test USDC to the agent's sandbox wallet. Only works with sk_test_ keys.
const credit = await client.sandbox.fundAgent("agt_01J9QKE...", {
  amount_usdc: "100.00",
});

console.log(credit.balance_usdc); // "100.00"
Python
from blockchain0x import Client
import os

client = Client(api_key=os.environ["BLOCKCHAIN0X_API_KEY"])

credit = client.sandbox.fund_agent(
    "agt_01J9QKE...",
    amount_usdc="100.00",
)
print(credit.balance_usdc)  # "100.00"
STEP 3 OF 5

Drive payments deterministically.

On live, payment events arrive when the chain confirms - which can take a few seconds and is non-deterministic. For tests, you want determinism: fire payment.received now, then payment.confirmed, or jump straight to payment.failed. The sandbox.simulatePayment endpoint does exactly this.

TypeScript
// Force a 'simulated' payment to walk through the full event lifecycle
// without holding for chain confirmations.
const sim = await client.sandbox.simulatePayment({
  payment_request_id: "pr_01J9R6Y...",
  outcome: "confirmed",          // "confirmed" | "received" | "failed"
});

console.log(sim.events);
// [
//   { type: "payment.received",  fired_at: "..." },
//   { type: "payment.confirmed", fired_at: "..." },
// ]
Python
sim = client.sandbox.simulate_payment(
    payment_request_id="pr_01J9R6Y...",
    outcome="confirmed",   # "confirmed" | "received" | "failed"
)

print(sim.events)
# [
#   {"type": "payment.received",  "fired_at": "..."},
#   {"type": "payment.confirmed", "fired_at": "..."},
# ]

Three scenarios to exercise at minimum: confirmed (happy path), failed (the path most teams ignore), and a webhook retry (force a 500 from your handler the first time and 200 the second, then verify your idempotency code skipped the duplicate work).

STEP 4 OF 5

Tunnel webhooks to your local handler.

The sandbox sends real webhooks to whatever URL you configured. For local development, give it an HTTPS tunnel to your laptop. ngrok is the simplest option; any reverse-tunnel tool works.

# Tunnel your local webhook endpoint to a public HTTPS URL.
$ ngrok http 3000

# Forwarding   https://abc123.ngrok.app -> http://localhost:3000

# Paste the URL in the dashboard under Webhooks - the sandbox
# uses the same endpoint config as live, just different keys.

The sandbox keeps a separate webhook URL setting from live, so you can leave production pointed at your real endpoint while your local tunnel handles test events.

STEP 5 OF 5

Fail fast on misconfigured keys.

The single most common production incident around test/live keys is silent: a deploy lands with a test key, no payments come through, alerts only fire after the next business day. Block this at boot: refuse to start if the env and the key prefix do not match.

TypeScript
// Fail fast if test/live get mixed up.
const apiKey = process.env.BLOCKCHAIN0X_API_KEY!;
const env = process.env.NODE_ENV;

if (env === "production" && apiKey.startsWith("sk_test_")) {
  throw new Error("Test key in production environment - aborting boot.");
}
if (env !== "production" && apiKey.startsWith("sk_live_")) {
  throw new Error("Live key in non-production environment - aborting boot.");
}
Python
import os, sys

api_key = os.environ["BLOCKCHAIN0X_API_KEY"]
env = os.environ.get("ENV", "development")

if env == "production" and api_key.startswith("sk_test_"):
    sys.exit("Test key in production environment - aborting boot.")
if env != "production" and api_key.startswith("sk_live_"):
    sys.exit("Live key in non-production environment - aborting boot.")
COMMON PITFALLS

Five testing mistakes that bite later.

Forgetting Base Sepolia is its own chain

Sandbox transactions land on Base Sepolia, not Base mainnet. The block explorers, the wallet addresses, the gas tokens are all separate. A common confusion is to copy a real Base address into a sandbox test, watch it fail, and think the API is broken. Use addresses that exist on Base Sepolia (the SDK's sandbox.fundAgent gives you a working one).

Not testing the failure paths

Most teams test the happy-path payment-confirmed flow, ship, and find out two weeks later that their payment.failed handler crashes the worker. Sandbox simulatePayment supports 'failed' as an outcome - run it. Run the abandonment flow. Force a 500 from your handler and verify the retry. Test environments are cheap; production debugging is expensive.

Webhook URL still pointing at ngrok in production

Switching key prefixes is easy to remember; updating the webhook URL is easy to forget. If you go live with the URL still pointed at the ngrok tunnel from your laptop, the first production payment fires a webhook into the void. Treat the webhook URL change as part of the deploy checklist, not a one-time setting.

Trusting sandbox timing as a proxy for live timing

Sandbox confirmations land slightly slower than live (the test chain is slower than Base mainnet), and gas is sponsored for everyone regardless of plan tier. Do not use sandbox to load-test live throughput, and do not assume your sandbox latency is what you will see in production. Use a small-amount mainnet smoke test for the real numbers.

Leaving test fixtures in shared databases

If your dev and prod environments share a database (don't), test events land in the same table as live events and break your idempotency dedupe (the event ID prefix is different but the row is real). At minimum, isolate the webhook_events table per environment. Better: separate DBs entirely. This is one of those rules that seems excessive until it bites once.

NEXT STEPS

Once the sandbox is in your dev loop.

With a healthy test loop in place, the remaining work is mostly hardening: robust webhook handling under load, a final security checklist, and migrations from any prior payment provider you may be running alongside.

Full reference at docs.blockchain0x.com. Sandbox details: Base chain glossary. Product surface: Payment API.

Last reviewed: 2026-05-15. Published under CC BY 4.0.

Test it before you ship it.

Full sandbox: test keys, Base Sepolia, simulatable lifecycle. Free.