Stablecoin payin API sandbox testing checklist: 20 cases before you go live
A payin API testing checklist with 20 sandbox cases: happy path, failed and refunded payins, duplicate webhooks, expired quotes, plus a go-live list.
To test a stablecoin payin API, run the happy path first. Then run every failure path: duplicate webhooks, delayed deposits, mismatched amounts, rejected KYC, wrong network, and expired quotes. After each case, confirm your ledger and customer state are still correct. BlindPay's development instances let you force most of these without real money.
This checklist covers the payin direction. For the general gap between sandbox and production, read stablecoin API sandbox vs production. For virtual accounts specifically, see how to test a virtual accounts API integration.
Key takeaways
- Development payins complete about 30 seconds after creation and deliver the USDB test token. Amounts
66600and77700forcefailedandrefunded. - Some cases need fixtures, not the sandbox: late deposits and mismatched amounts cannot be forced.
- Your webhook handler needs the most testing: replays, disorder, bad signatures, and 500s.
- Test the write side too: quote expiry, idempotent retries, and the 40 requests per minute development limit.
- The sandbox does not prove production timing. Plan one small supervised payin per rail.
What should I set up first?
Set up a development instance, an API key, and a webhook endpoint that logs everything before any business logic exists. Keys belong to one instance, and every request goes to https://api.blindpay.com, so the key alone decides the environment.
Register the endpoint with POST /v1/instances/{instance_id}/webhook-endpoints. An empty events array receives every event. Fetch the signing secret from the /secret route. The first working test is a payin that you can see arriving in your log with its svix-id, payin.new, and payin.complete events. The exact calls are in the integration quickstart.
Also create fixtures for what the sandbox cannot do: a payin that stays processing past its window, and a payin whose receiver_amount does not match what you expected. Save real payload shapes from the events dashboard and replay them against your handler.
Which test cases are required?
Run all twenty. Cases marked "fixture" cannot be forced in the sandbox, so inject the payload yourself.
| # | Case | Steps | Expected result | Pass criteria |
|---|---|---|---|---|
| 1 | Happy path ACH payin | Create customer, wallet, quote, and payin | Completes after about 30 seconds, USDB delivered; memo_code shows <development> | One credit, status completed, placeholder never shown to a payer |
| 2 | Failed payin | Quote with request_amount 66600 | payin.complete with failed | Payment closed, no credit, no success message |
| 3 | Refunded payin | Quote with request_amount 77700 | payin.complete with refunded | Payment closed as refunded, customer notified |
| 4 | Duplicate delivery | Replay a payin.complete from the events dashboard | Same svix-id arrives again | Exactly one credit |
| 5 | Out-of-order event | Fixture: send payin.update (processing) after completed | Event ignored | Status stays completed |
| 6 | Bad signature | Send a payload with a wrong svix-signature | Rejected | 4xx response, no state change |
| 7 | Stale timestamp | Fixture: valid signature, timestamp older than 5 minutes | Rejected | 4xx response, no state change |
| 8 | Handler returns 500 | Fail the handler once after verifying | BlindPay retries delivery | Second delivery processed once |
| 9 | Expired quote | Wait more than 5 minutes, then create the payin | Create is rejected | New quote requested, user sees a clear retry |
| 10 | Idempotent retry | Create a payin twice with one Idempotency-Key and one body | Stored response replays with Idempotency-Replayed: true | One payin exists |
| 11 | Changed body, same key | Reuse the key with a different body | 422 idempotency_key_payload_mismatch | Error surfaced, no second payin |
| 12 | In-flight duplicate | Send the same keyed request twice at once | 409 idempotency_key_in_flight with Retry-After | Client waits, then retries safely |
| 13 | Rejected KYC | Create a customer with first name Fail | Customer rejected | No quote attempted, new customer path works |
| 14 | Delayed deposit | Fixture: payin in processing past its window | Stuck-payin alert fires | Alert reaches a human |
| 15 | Mismatched amount | Fixture: ledger amount differs from receiver_amount | Reconciliation check flags it | Exception queued with the payin id |
| 16 | Wrong token or network | Quote USDT to a wallet on a network without it | Request rejected | Error shown, no payin created |
| 17 | Whole-unit rule | MXN quote with request_amount 10050 | request_amount_must_be_a_whole_currency_unit | Client rounds or validates first |
| 18 | Rate limit | Send more than 40 requests in a minute | 429 development_instance_rate_limit_exceeded | Client backs off using Retry-After |
| 19 | Virtual account deposit | Create an account, then call simulate-spei-deposit on a SPEI CLABE account | New payin, payin.new, mapped to the customer | Deposit tied to the right customer_id |
| 20 | Daily reconciliation | List payins with limit, compare with ledger | Zero unexplained differences | Exception queue empty or assigned |
Case 16 is worth a note. A payin quote never takes a network, because BlindPay reads it from the wallet, and the token and network must be a supported pair. Read the error text instead of hardcoding rules. The deeper logic for cases 4 to 8 and 14 to 15 is in payin webhooks, idempotency, and reconciliation, and the failure behaviour is covered in failed, late, or mismatched stablecoin payins.
How do I test webhooks locally?
Expose your local handler on a public https URL, because BlindPay rejects local and private addresses and accepts only https endpoints. For a first look at payloads before you have a handler, point an endpoint at a temporary request logger such as webhook.cool.
Three habits make local testing reliable:
- Verify against the raw body. Parsing and re-serializing JSON changes the bytes and breaks the signature.
- Use the events dashboard on your development instance to inspect delivery attempts and response codes, and to replay any event. A replay resends the exact original payload with a new delivery attempt.
- Keep your test and production endpoints separate. The development webhook configuration does not carry over to production.
Webhooks behave identically in development and production: the same event names, payload shapes, and signatures. Signature rules are in the webhooks docs.
What should I monitor at launch?
Watch the state of payments, not only errors. In the first weeks, track:
- Webhook non-
2xxresponses and signature failures - Payins in
processingbeyond the rail window (30 minutes for Pix, SPEI, Transfers, and PSE; 5 business days for ACH and wire) - Payins
on_holdfor more than 24 hours, because an unanswered request for information may end in a refund - Any terminal payin that changes state
- Daily reconciliation exceptions
- Share of quotes that expire before a payin is created
You can script these checks. The BlindPay CLI has blindpay payin_quotes create and blindpay payins create --quote-id qu_..., with --json output and exit codes 0 (success), 1 (user error), and 2 (API error), so a CI job can fail on a bad code. Agents connected to the MCP server can read payins with GetV1InstancesPayins and GetV1InstancesPayinsById. Point them at the read-only URL when they should only observe.
What belongs on the go-live checklist?
Copy this list into your release ticket.
- Production instance exists (it can take up to 3 business days to provision) and has its own API key.
- Real KYC documents are submitted for every customer; placeholder URLs removed.
-
tokenchanged fromUSDBtoUSDCorUSDT, and networks changed from testnets to mainnets. - Test amounts
66600and77700removed from your code. - A new webhook endpoint exists on the production instance, and its signing secret is stored.
- Signature check,
svix-iddedupe, and forward-only status logic are tested (cases 4 to 8). -
Idempotency-Keysent on every create call. - Stuck-payin, on-hold, and reconciliation alerts have an owner.
- Payer screens show real bank details, not
<development>. - One small supervised payin per rail has completed in production.
For tokens and networks, which blockchain network to use for payins helps with the choice.
Where does BlindPay fit?
BlindPay offers the pieces this checklist relies on: development instances with automatic KYC approval, a 30-second payin completion, forced outcomes at 66600 and 77700, a Fail name for KYC rejection, identical webhook behavior across environments, a replayable events dashboard, and idempotent create calls. Details are in the sandbox vs production docs.
The sandbox has limits. Real bank timing, real KYC review, and payer mistakes only appear in production, so keep the supervised first payin. BlindPay's pricing is a monthly plan plus per-transaction fees disclosed in the quote (see /pricing). To see how a vendor's tooling fits, start with what a stablecoin payin API is.
Sources
- BlindPay docs, Sandbox vs. production (checked on 2026-10-29)
- BlindPay docs, Webhooks (checked on 2026-10-29)
- BlindPay docs, BlindPay MCP tools reference (checked on 2026-10-29)
This article is for general information only and is not legal, tax, or financial advice.
FAQ
More payments articles
See all payments articlesStablecoin payin API pricing: fees to expect and how to compare them
Stablecoin API pricing for payins: the fee types to expect, a worked USD 10,000 example, a quote checklist, and what BlindPay charges.
How to choose a stablecoin payin API: a 12-question checklist
How to choose a stablecoin API provider for payins: 12 questions, a weighted scorecard, red flags, and a proof of concept plan for buyers.
How to evaluate a global checkout provider: 12 questions
A 2026 scorecard for choosing a global checkout provider: 12 questions on rails, pricing, sandbox, webhooks, compliance, settlement, and custody.