PlutoPay Docs
API Reference Dashboard ↗

#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.canceled and payout.* have always carried the field. payment.created, customer.* and dispute.* 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 missing is_test as unknown rather than as false.

#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_payments capability 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 active on 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

  1. Register a webhook endpoint (test mode) pointing at your receiver — e.g. a webhook.site bucket or a local tunnel.
  2. Create a payment with a sk_test_… key.
  3. Confirm it client-side with a test card, or use the terminal simulator.
  4. Observe payment.created then payment.succeeded (or payment.failed) hit your endpoint.
  5. Verify the signature on each delivery.