Kivel

Test an agent that pays

Test agent spending with fake wallets, human approval, HTTP 402 paywalls and checkout events. No real funds, blockchain transactions or card collection.

Updated 8 November 2026

Use fake money from start to finish

Test wallets, paywalls and checkouts are temporary workspace resources. They use the same resource, storage and retention allowances as mocks. Everything is test mode. There are no real funds, blockchain transactions, payment accounts or private keys. A successful test receipt proves only that this simulated flow completed.

Give the agent a test wallet and paywall

Create a wallet with a name and starting USD test balance. The example uses a six-unit paywall, above the default Free approval threshold and within its per-payment limit. Owners on other plans can choose a price or threshold that exercises approval. Create the paywall with its price and response. An ordinary request to its URL returns HTTP 402 with x402 v1 payment instructions. Any HTTP method and sub-path work.

MCP calls
create_test_wallet({"name":"Research agent","balance":100})
create_test_paywall({"name":"Article","amount":6,"response":{"status":200,"body":{"article":"Test content"}}})

GET https://kivel.io/pay/<paywall-id>/article
→ 402 {"x402Version":1,"testMode":true,"accepts":[...]}

Pay, approve and get the content

Call wallet_pay with the returned paywall URL and a unique idempotencyKey. The configured price is used when amount is omitted. Payments above the wallet threshold return an approval link. An owner or admin opens it in the browser and approves or rejects. Agent keys cannot answer, even their own requests. MCP waits briefly; repeat the identical payment with the same idempotency key after the human answers.

By default wallet_pay authorizes the test amount, retries the paywall and returns its content. To exercise a client retry yourself, set authorizeOnly to true, then send the returned paymentHeader as X-PAYMENT to that exact URL within five minutes. An unused authorization reserves balance. Redemption debits once; replay is rejected. Wallet setting changes invalidate pending payments.

Pay through HTTP
POST /api/test-wallets/<wallet-id>/pay
{"payee":"https://kivel.io/pay/<paywall-id>/article","memo":"Read the test article","idempotencyKey":"article-attempt-1"}

202 → share answerUrl → human approves → repeat the same request
200 → {"testMode":true,"status":"succeeded","content":{"article":"Test content"}}

Keep human controls separate from agent actions

Only a signed-in owner or admin can change per-payment limits, daily limits and approval thresholds. API keys must omit these controls, including during wallet creation. They can read the settings, spend within them and add free test balance. Daily spending resets at midnight UTC. A wallet cannot pay more than its available balance or limits, even after approval.

Default test wallet controls, in fake USD units
PlanPer paymentPer UTC dayApproval aboveTop-ups per minute
Free1050510
Pro1005002530
Team500250010060

Test an x402 client without settlement

The paywall uses the x402 v1 HTTP transport: HTTP 402 with accepts, a base64 JSON X-PAYMENT request header and a base64 JSON X-PAYMENT-RESPONSE receipt. Requirements include scheme exact, network base-sepolia, a synthetic asset and recipient, amount and maxAmountRequired in six-decimal test units, resource URL and description.

Wallet mode accepts only opaque, HMAC-protected Kivel test wallet tokens. Permissive mode validates the structure of a v1 authorization with signature, from, to, value, validAfter, validBefore and nonce. It does not verify signatures, check balances, reject repeated fixture payloads or settle funds. Use it for client serialization tests. This is not a blockchain facilitator or the v2 PAYMENT-SIGNATURE transport.

Transport specification
https://github.com/x402-foundation/x402/blob/main/specs/transports-v1/http.md

Exercise checkout outcomes and verification

Create a checkout with an item, amount, optional currency and webhook URL. Names and descriptions are plain text limited to 80 characters. Open its public hosted page and choose an outcome. The verification outcome opens a second confirm page. Success and cancel destinations are explicit links with an origin notice, never automatic external redirects.

Agents use complete_test_checkout with a fake card fixture and idempotencyKey, or wallet_pay with the checkout URL. Test wallets support USD only. Generated Luhn-valid cards succeed by default. An expired supplied fixture date produces the expired outcome. Card numbers, expiry and CVC are never retained in checkout state or call logs; only outcome and last four digits remain.

Fake card fixtures accepted only through the API and MCP
NumberOutcome
4242424242424242Success
4000000000000002Declined
4000000000009995Insufficient funds
4000002500003155Requires verification
4000000000000069Expired

Verify the event and inspect what happened

Checkout creation returns webhookSecret once. Save it. Completion sends payment.succeeded or payment.failed, with testMode true, an event ID and generic payment data. The amount is integer minor units. Verify X-Kivel-Signature before trusting the event: parse t and v1, calculate HMAC-SHA256 over the exact string timestamp + "." + raw JSON body, then compare the hex signature in constant time. Reject stale timestamps and deduplicate event IDs.

Delivery uses the existing public-network protections and forwarding retries: immediate, then after two and ten seconds for 429, server errors or connection failures. Other client errors are final. Delivery attempts are visible in the checkout detail and API. Delivery is best effort, like webhook forwarding; no durable queue runs after a process restart.

The dashboard Payments (test) page has Wallets, Paywalls and Checkouts tabs. Wallet ledgers include payments, top-ups, refusals and approval events. Public endpoint calls are available through inspect_mock_api. Deleting or expiring a resource removes its state and history.

SDK and CLI
const wallet = await client.createTestWallet({ name: "Research agent", balance: 100 });
await client.walletPay(wallet.id, { payee: paywall.endpointUrl, idempotencyKey: "article-1" });
await client.listWalletTransactions(wallet.id);

kivel test-wallet create < wallet.json
kivel test-wallet pay <wallet-id> < payment.json
kivel test-wallet transactions <wallet-id>
kivel test-checkout complete <checkout-id> < fake-card.json