Test mode
One test mode, two ways to pay — test wallets, or a real wallet on a testnet.
Every merchant has two API keys: a live key (tp_live_...) and a test key (tp_test_...). Both are shown once, in the dashboard (Settings → API keys), at creation or regeneration.
Requests authenticated with a tp_test_... key — and everything you create in the dashboard with the Test mode toggle on — run in test mode (called sandbox in the API: isSandbox on objects and webhook payloads). No real funds ever move in test mode.
The base URL is the same for both keys. Switching between test and live is only a matter of which key you send.
Two ways to pay
Test mode is a single mode. What differs is how the payer pays, and every test checkout offers the first way:
| Test wallet | Testnet wallet | |
|---|---|---|
| What it is | A fixed address that stands in for a wallet, like a test card | A real browser wallet (MetaMask, Phantom…) on Base Sepolia or Solana Devnet |
| Blockchain | Simulated — synthetic 0xdead... hashes | Real testnet transactions, visible on the block explorer |
| You need | Nothing | Faucet ETH/SOL for gas and faucet USDC |
| Declines on demand | Yes — pick a declining wallet | Only by draining the wallet |
| Available on | Every test-mode checkout, any token | Accounts enabled by Tidepay, on testnet-priced offers |
| Best for | Building the integration, webhooks, failure paths, automated tests | A final rehearsal of the real approve → pull → split flow |
Testnet wallets are enabled per account by the Tidepay team — every testnet charge spends real testnet gas on Tidepay's side. Without it, test wallets cover everything, testnet-priced offers included (they are simulated too).
For an enabled account, a test-mode checkout priced in a testnet token shows both: the test wallets first, then or pay on-chain with a testnet wallet. On a test-mode checkout priced in a mainnet token (e.g. usdc-base under a test key), only the test wallets are offered, since a real wallet there would move real funds.
In the dashboard, Settings → Payments lists the test wallets while the Test mode toggle is on. For an enabled account it also shows the testnet option: adding a receiving wallet on Base Sepolia or Solana Devnet switches on the testnet option for that token. Without one, test offers are created against a placeholder payout address and can only be paid with test wallets — the placeholder is refused on mainnet tokens.
See Testnet chains for the faucets and the testnet walkthrough.
Test wallets
Test wallets play the role of test card numbers. Give a customer one of these addresses — through POST /customers, PATCH /customers/{customerId}, or by picking it on the checkout page — and every charge against that customer produces the listed result:
| Result | EVM address | Solana address |
|---|---|---|
| Payment succeeds | 0x4242424242424242424242424242424242424242 | TidepayTestSuccess1111111111111111111111111 |
Declined: insufficient_balance | 0x4000000000000000000000000000000000009995 | TidepayTestNoFunds1111111111111111111111111 |
Declined: insufficient_allowance | 0x4000000000000000000000000000000000000002 | TidepayTestNoAuth11111111111111111111111111 |
- A declined charge records a
failedinvoice with thatfailureReason, moves the subscription topast_due, and firespayment.failed— exactly what a real wallet without funds or approval would cause. - On a mainnet token, any other address also succeeds in test mode, so existing test data keeps working. On a testnet token, any other address is treated as a real testnet wallet and charged on-chain.
- Several test customers may share the same test wallet; the one-wallet-per-customer rule does not apply to them.
- Test wallets are rejected with
400on a live key, the same way live mode refuses test cards.
curl https://api.tidepay.cc/v1/customers \
-H "X-API-Key: tp_test_..." \
-H "Content-Type: application/json" \
-d '{
"email": "declined@example.com",
"wallets": [
{ "chainFamily": "evm", "address": "0x4000000000000000000000000000000000009995" }
]
}'Paying from the checkout page
Every test-mode checkout — subscriptions (subscribeUrl), one-time payments (checkoutUrl) and payment links (/pay/...) — lists the test wallets above. Picking one pays with it, through the same backend path a real payment takes, so the result shows up in the dashboard and in your webhooks exactly as it would live. On a testnet-priced checkout, the regular wallet-connect flow follows below them.
Subscriptions
- Payment succeeds — records a
settledinvoice, moves the subscription toactive, advancescurrentPeriodEndby one interval, and firespayment.succeededandsubscription.active. - Declined — records a
failedinvoice with the matchingfailureReason, moves the subscription topast_due, and firespayment.failed.
The chosen wallet stays on the customer, so later renewals keep producing the same result — like a customer who saved a test card.
One-time payments and payment links
- Payment succeeds — the payment becomes
settledandpayment.succeededfires. - Declined — the payment becomes
failedwith the matchingfailureReasonandpayment.failedfires. A one-time payment is final, so open a new link to try another wallet.
To test a payment link end to end: create the offer with your tp_test_... key (or in the dashboard with Test mode on), open its link, and pick a test wallet — or, for a testnet-priced offer, connect a funded testnet wallet.
Advancing time
Subscriptions renew on their billing interval. Rather than waiting a month, fast-forward a test subscription with POST /sandbox/advance — the equivalent of a test clock:
curl https://api.tidepay.cc/v1/sandbox/advance \
-H "X-API-Key: tp_test_..." \
-H "Content-Type: application/json" \
-d '{ "subscriptionId": "sub_...", "cycles": 3 }'Each cycle runs the same renewal charge the billing cron would, with the same invoices and webhooks. cycles is 1–12 (default 1). It stops at the first cycle that does not settle, so a subscription paying from a declining test wallet ends up past_due after one cycle. The response lists each cycle's outcome and the subscription's resulting status and currentPeriodEnd.
POST /sandbox/charge runs exactly one cycle and is kept for existing integrations; POST /sandbox/advance with cycles: 1 does the same.
Both endpoints accept only test keys — a live key gets 403.
What else differs in test mode
- For a mainnet token,
POST /subscriptionsreturns a placeholderoperatorWallet(0x000000000000000000000000000000005a4d42for EVM, a fixed placeholder base58 address for Solana). Never send real token approvals to it — it holds no authority and is not monitored. For a testnet token it returns the real operator, since a testnet wallet approves it for real. - Simulated charges (any test wallet, or any mainnet token) carry synthetic
pullTxHash/merchantTransferTxHash/platformTransferTxHashvalues — prefixed0xdead...for EVM, a recognizable fake signature for Solana. They never resolve on a block explorer. Testnet-wallet charges carry real testnet hashes. - Test and live data are fully isolated: offers, customers, subscriptions and payments created with the test key are invisible to the live key, and vice versa.
- Test subscriptions and their payments are deleted automatically after 30 days.
Webhooks
Test events can go to their own endpoint: the dashboard (Settings → Business) takes an optional Sandbox webhook URL alongside the live one. Leave it empty and test events go to your live URL. Live events never go to the sandbox URL.
Test events are signed exactly like live ones (x-tidepay-signature, HMAC-SHA256 over ${timestamp}.${rawBody}), and every payload carries a top-level isSandbox: boolean so a shared endpoint can tell them apart. See Webhooks.
Going live
Test and live share no data, so switching over is deliberate:
- Recreate your offers and prices with your
tp_live_...key — test-key offers cannot sell for real. - Send the live key instead of the test key. The base URL does not change; see Authentication.
- Confirm your webhook handler accepts events with
isSandbox: false. - Make sure the destination wallet on each live price is one you control on the mainnet chain you priced in.