#Testing
Build and verify your entire integration without moving real money. Test mode rides the same endpoints as live — only the API key differs.
#Test mode
Use a test secret key (sk_test_…). Everything you create is flagged is_test: true and isolated from live data. Webhooks fire in test mode too, so you can exercise your receiver end-to-end.
Your live account is your test account. There is no separate sandbox to sign up for, no
second merchant record, and no extra onboarding. The same account you take real payments
with also has a test side — the only thing that decides which one you are touching is the
key you send. A sk_test_… request creates test payments that settle to your account's test
balance with the same fee split as live, so what you verify in test is the same code path
that runs in production.
Switch the Dashboard between Live and Test with the toggle to see each set of data.
#Webhooks are scoped to the mode that registered them
An endpoint belongs to the mode it was created in:
- Register it with a test key and it receives test events only.
- Register it with a live key and it receives live events only.
This is deliberate, and it is the reason you can test safely against a running integration: your production receiver will never be handed a test payment. It also means a test endpoint is something you register separately — pointing a live endpoint at your staging server does not make it a test endpoint.
The creation response echoes is_test so you can confirm which mode you just registered in:
curl -X POST https://plutopayus.com/api/v1/webhook-endpoints \
-H "Authorization: Bearer sk_test_your_key_here" \
-d url=https://staging.example.com/webhooks/plutopay
{ "data": { "id": "…", "url": "…", "is_test": true, "status": "active" } }
#Telling test events from live ones
Every event carries data.is_test. Check that field rather than inferring mode from
which endpoint received it, from the amount, or from the time of day:
{
"type": "payment.succeeded",
"data": { "reference": "txn_…", "amount": 4750, "is_test": true }
}
Events delivered before 2026-08-31 may not have it.
payment.succeeded,payment.failed,payment.canceledandpayout.*have always carried the field.payment.created,customer.*anddispute.*did not, so historical records of those types have no mode marker and cannot be classified after the fact. If you are backfilling from stored events, treat a missingis_testas unknown rather than asfalse.
#Test cards
When confirming a payment client-side — with the Stripe test publishable key from GET /v1/merchant — use Stripe's standard test cards (any future expiry, any CVC, any ZIP):
| Number | Behavior |
|---|---|
4242 4242 4242 4242 |
Visa — succeeds. |
4000 0000 0000 0002 |
Card declined (card_declined). |
4000 0000 0000 9995 |
Declined — insufficient funds. |
4000 0025 0000 3155 |
Requires 3D Secure authentication. |
The full list is in Stripe's testing documentation. For ACH, see the ACH test payment methods — and read the warning immediately below first.
#What test mode does NOT prove
Test mode proves your integration. It does not prove that your account is approved for a given payment method.
A successful test ACH payment is not evidence that live ACH will work.
ACH debits require the
us_bank_account_ach_paymentscapability to be active on the connected account PlutoPay created for you — which is a different object from any Stripe account you may already hold directly. A direct Stripe account that has processed ACH for years says nothing about the capability on the connected one; they carry their own capabilities and are reviewed separately. Stripe's test environment does not enforce capabilities, so an ACH payment can succeed in test against an account that would be declined in live. As of this writing no connected account on the platform holds that capability.Before you build a launch plan around ACH, ask us to confirm the capability is
activeon your account. Do not infer it from a green test payment — that inference is the exact mistake this warning exists to prevent.
The same caution applies to any payment method your account has not been explicitly enabled for. Cards are enabled by default; ACH is not.
#Simulating a terminal payment
Physical readers aren't required in test mode. First create a simulated reader — once —
with POST /v1/terminals and "registration_code": "simulated-wpe" (the Terminal
guide walks through it). Then, after
creating and processing a terminal payment, present a test card on
it:
POST /v1/terminal/simulate-payment
| Field | Type | Required |
|---|---|---|
reader_id |
string | ✔ |
{ "data": { "reader_id": "tmr_simulated_…", "status": "succeeded" } }
This endpoint is test-mode only — calling it with a live key returns 400.
#A full test loop
- Register a webhook endpoint (test mode) pointing at your receiver — e.g. a webhook.site bucket or a local tunnel.
- Create a payment with a
sk_test_…key. - Confirm it client-side with a test card, or use the terminal simulator.
- Observe
payment.createdthenpayment.succeeded(orpayment.failed) hit your endpoint. - Verify the signature on each delivery.