[{"data":1,"prerenderedAt":944},["ShallowReactive",2],{"content-\u002Fresources\u002Fmore\u002Fpayin-api-sandbox-testing-checklist":3,"resources-category-payin-api-sandbox-testing-checklist":929},{"title":4,"seoTitle":5,"description":6,"date":7,"updated":8,"category":9,"faq":10,"howto":29,"body":50},"Stablecoin payin API sandbox testing checklist: 20 cases before you go live","Payin API testing checklist: 20 sandbox cases","A payin API testing checklist with 20 sandbox cases: happy path, failed and refunded payins, duplicate webhooks, expired quotes, plus a go-live list.","2026-10-29",null,"payments",[11,14,17,20,23,26],{"q":12,"a":13},"What should a payin API testing checklist cover?","The happy path, then every failure path: a failed payin, a refunded payin, duplicate and out-of-order webhooks, a bad signature, an expired quote, a retried create call, a rejected KYC, and a wrong token or network. After each case, confirm your ledger and customer state are still correct. Add a reconciliation run at the end.",{"q":15,"a":16},"How do I force a failed or refunded payin on BlindPay?","On a development instance, set the payin quote's request_amount to 66600 ($666.00) for a failed payin, or 77700 ($777.00) for a refunded one. Any other amount completes about 30 seconds after the payin is created. These amounts only work in development, and production processes the real amount you send.",{"q":18,"a":19},"How do I test a rejected KYC in the BlindPay sandbox?","Development customers are approved automatically, whatever documents you submit. To force a rejection, set the first name (individuals) or legal name (businesses) to Fail. You cannot update a rejected customer's KYC data, so create a new customer with corrected information and keep your onboarding code ready for that path.",{"q":21,"a":22},"Can I test webhooks locally?","Webhook URLs must be https and local or private addresses are rejected, so expose your local handler through a public https tunnel, or point a temporary endpoint at a request logger such as webhook.cool to inspect payloads first. The events dashboard shows every delivery attempt and lets you replay any event.",{"q":24,"a":25},"What can't the sandbox tell me about payins?","It cannot show real bank timing, real KYC review, or a payer who sends the wrong amount. Development payins finish in about 30 seconds, and memo and Pix codes are the placeholder text development. Test late and mismatched deposits with fixtures, then run one small supervised payin per rail in production.",{"q":27,"a":28},"Can an AI agent run these tests for me?","Yes, within limits. Connected to BlindPay's MCP server on a development instance, an agent can call tools such as PostV1InstancesPayinQuotes and PostV1InstancesPayinsEvm, then read results with GetV1InstancesPayins. Keep human confirmation for any production money movement, because the server does not block a call on its own.",{"name":30,"steps":31},"How to test a stablecoin payin API before going live",[32,35,38,41,44,47],{"name":33,"text":34},"Set up a development instance and a webhook endpoint","Create a development instance and key, register an HTTPS webhook endpoint, and log every delivery with its svix-id before you write any business logic.",{"name":36,"text":37},"Run the happy path","Create a customer, a wallet, a payin quote, and a payin. Confirm payin.new and payin.complete arrive and your ledger credits the customer once.",{"name":39,"text":40},"Force the failure outcomes","Use request_amount 66600 for a failed payin and 77700 for a refunded one. Confirm your state machine and customer messages handle both.",{"name":42,"text":43},"Attack your webhook handler","Replay events, send them out of order, send a bad signature and a stale timestamp, and return a 500. Your ledger must stay correct each time.",{"name":45,"text":46},"Test quotes, idempotency, and limits","Let a quote expire, retry creates with the same Idempotency-Key, send a changed body, and exceed the 40 requests per minute development limit.",{"name":48,"text":49},"Run the go-live checklist","Check production keys, real KYC documents, USDC or USDT tokens, mainnet networks, a new webhook endpoint, and your alerts, then run one small supervised payin per rail.",{"type":51,"value":52,"toc":917},"minimark",[53,57,71,76,110,114,121,152,163,167,170,633,645,649,656,659,671,678,682,685,716,747,751,754,849,857,861,876,887,891,911],[54,55,56],"p",{},"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.",[54,58,59,60,65,66,70],{},"This checklist covers the payin direction. For the general gap between sandbox and production, read ",[61,62,64],"a",{"href":63},"\u002Fresources\u002Fmore\u002Fstablecoin-api-sandbox-vs-production","stablecoin API sandbox vs production",". For virtual accounts specifically, see ",[61,67,69],{"href":68},"\u002Fresources\u002Fmore\u002Fhow-to-test-virtual-accounts-sandbox","how to test a virtual accounts API integration",".",[72,73,75],"h2",{"id":74},"key-takeaways","Key takeaways",[77,78,79,98,101,104,107],"ul",{},[80,81,82,83,87,88,91,92,87,95,70],"li",{},"Development payins complete about 30 seconds after creation and deliver the USDB test token. Amounts ",[84,85,86],"code",{},"66600"," and ",[84,89,90],{},"77700"," force ",[84,93,94],{},"failed",[84,96,97],{},"refunded",[80,99,100],{},"Some cases need fixtures, not the sandbox: late deposits and mismatched amounts cannot be forced.",[80,102,103],{},"Your webhook handler needs the most testing: replays, disorder, bad signatures, and 500s.",[80,105,106],{},"Test the write side too: quote expiry, idempotent retries, and the 40 requests per minute development limit.",[80,108,109],{},"The sandbox does not prove production timing. Plan one small supervised payin per rail.",[72,111,113],{"id":112},"what-should-i-set-up-first","What should I set up first?",[54,115,116,117,120],{},"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 ",[84,118,119],{},"https:\u002F\u002Fapi.blindpay.com",", so the key alone decides the environment.",[54,122,123,124,127,128,131,132,135,136,139,140,143,144,147,148,70],{},"Register the endpoint with ",[84,125,126],{},"POST \u002Fv1\u002Finstances\u002F{instance_id}\u002Fwebhook-endpoints",". An empty ",[84,129,130],{},"events"," array receives every event. Fetch the signing secret from the ",[84,133,134],{},"\u002Fsecret"," route. The first working test is a payin that you can see arriving in your log with its ",[84,137,138],{},"svix-id",", ",[84,141,142],{},"payin.new",", and ",[84,145,146],{},"payin.complete"," events. The exact calls are in the ",[61,149,151],{"href":150},"\u002Fresources\u002Fmore\u002Fstablecoin-payin-api-integration-quickstart","integration quickstart",[54,153,154,155,158,159,162],{},"Also create fixtures for what the sandbox cannot do: a payin that stays ",[84,156,157],{},"processing"," past its window, and a payin whose ",[84,160,161],{},"receiver_amount"," does not match what you expected. Save real payload shapes from the events dashboard and replay them against your handler.",[72,164,166],{"id":165},"which-test-cases-are-required","Which test cases are required?",[54,168,169],{},"Run all twenty. Cases marked \"fixture\" cannot be forced in the sandbox, so inject the payload yourself.",[171,172,173,195],"table",{},[174,175,176],"thead",{},[177,178,179,183,186,189,192],"tr",{},[180,181,182],"th",{},"#",[180,184,185],{},"Case",[180,187,188],{},"Steps",[180,190,191],{},"Expected result",[180,193,194],{},"Pass criteria",[196,197,198,227,252,274,297,325,345,360,377,394,418,437,459,482,502,521,542,564,585,612],"tbody",{},[177,199,200,204,207,210,220],{},[201,202,203],"td",{},"1",[201,205,206],{},"Happy path ACH payin",[201,208,209],{},"Create customer, wallet, quote, and payin",[201,211,212,213,216,217],{},"Completes after about 30 seconds, USDB delivered; ",[84,214,215],{},"memo_code"," shows ",[84,218,219],{},"\u003Cdevelopment>",[201,221,222,223,226],{},"One credit, status ",[84,224,225],{},"completed",", placeholder never shown to a payer",[177,228,229,232,235,242,249],{},[201,230,231],{},"2",[201,233,234],{},"Failed payin",[201,236,237,238,241],{},"Quote with ",[84,239,240],{},"request_amount"," 66600",[201,243,244,246,247],{},[84,245,146],{}," with ",[84,248,94],{},[201,250,251],{},"Payment closed, no credit, no success message",[177,253,254,257,260,265,271],{},[201,255,256],{},"3",[201,258,259],{},"Refunded payin",[201,261,237,262,264],{},[84,263,240],{}," 77700",[201,266,267,246,269],{},[84,268,146],{},[84,270,97],{},[201,272,273],{},"Payment closed as refunded, customer notified",[177,275,276,279,282,288,294],{},[201,277,278],{},"4",[201,280,281],{},"Duplicate delivery",[201,283,284,285,287],{},"Replay a ",[84,286,146],{}," from the events dashboard",[201,289,290,291,293],{},"Same ",[84,292,138],{}," arrives again",[201,295,296],{},"Exactly one credit",[177,298,299,302,305,317,320],{},[201,300,301],{},"5",[201,303,304],{},"Out-of-order event",[201,306,307,308,311,312,314,315],{},"Fixture: send ",[84,309,310],{},"payin.update"," (",[84,313,157],{},") after ",[84,316,225],{},[201,318,319],{},"Event ignored",[201,321,322,323],{},"Status stays ",[84,324,225],{},[177,326,327,330,333,339,342],{},[201,328,329],{},"6",[201,331,332],{},"Bad signature",[201,334,335,336],{},"Send a payload with a wrong ",[84,337,338],{},"svix-signature",[201,340,341],{},"Rejected",[201,343,344],{},"4xx response, no state change",[177,346,347,350,353,356,358],{},[201,348,349],{},"7",[201,351,352],{},"Stale timestamp",[201,354,355],{},"Fixture: valid signature, timestamp older than 5 minutes",[201,357,341],{},[201,359,344],{},[177,361,362,365,368,371,374],{},[201,363,364],{},"8",[201,366,367],{},"Handler returns 500",[201,369,370],{},"Fail the handler once after verifying",[201,372,373],{},"BlindPay retries delivery",[201,375,376],{},"Second delivery processed once",[177,378,379,382,385,388,391],{},[201,380,381],{},"9",[201,383,384],{},"Expired quote",[201,386,387],{},"Wait more than 5 minutes, then create the payin",[201,389,390],{},"Create is rejected",[201,392,393],{},"New quote requested, user sees a clear retry",[177,395,396,399,402,409,415],{},[201,397,398],{},"10",[201,400,401],{},"Idempotent retry",[201,403,404,405,408],{},"Create a payin twice with one ",[84,406,407],{},"Idempotency-Key"," and one body",[201,410,411,412],{},"Stored response replays with ",[84,413,414],{},"Idempotency-Replayed: true",[201,416,417],{},"One payin exists",[177,419,420,423,426,429,434],{},[201,421,422],{},"11",[201,424,425],{},"Changed body, same key",[201,427,428],{},"Reuse the key with a different body",[201,430,431],{},[84,432,433],{},"422 idempotency_key_payload_mismatch",[201,435,436],{},"Error surfaced, no second payin",[177,438,439,442,445,448,456],{},[201,440,441],{},"12",[201,443,444],{},"In-flight duplicate",[201,446,447],{},"Send the same keyed request twice at once",[201,449,450,246,453],{},[84,451,452],{},"409 idempotency_key_in_flight",[84,454,455],{},"Retry-After",[201,457,458],{},"Client waits, then retries safely",[177,460,461,464,467,473,479],{},[201,462,463],{},"13",[201,465,466],{},"Rejected KYC",[201,468,469,470],{},"Create a customer with first name ",[84,471,472],{},"Fail",[201,474,475,476],{},"Customer ",[84,477,478],{},"rejected",[201,480,481],{},"No quote attempted, new customer path works",[177,483,484,487,490,496,499],{},[201,485,486],{},"14",[201,488,489],{},"Delayed deposit",[201,491,492,493,495],{},"Fixture: payin in ",[84,494,157],{}," past its window",[201,497,498],{},"Stuck-payin alert fires",[201,500,501],{},"Alert reaches a human",[177,503,504,507,510,515,518],{},[201,505,506],{},"15",[201,508,509],{},"Mismatched amount",[201,511,512,513],{},"Fixture: ledger amount differs from ",[84,514,161],{},[201,516,517],{},"Reconciliation check flags it",[201,519,520],{},"Exception queued with the payin id",[177,522,523,526,529,536,539],{},[201,524,525],{},"16",[201,527,528],{},"Wrong token or network",[201,530,531,532,535],{},"Quote ",[84,533,534],{},"USDT"," to a wallet on a network without it",[201,537,538],{},"Request rejected",[201,540,541],{},"Error shown, no payin created",[177,543,544,547,550,556,561],{},[201,545,546],{},"17",[201,548,549],{},"Whole-unit rule",[201,551,552,553,555],{},"MXN quote with ",[84,554,240],{}," 10050",[201,557,558],{},[84,559,560],{},"request_amount_must_be_a_whole_currency_unit",[201,562,563],{},"Client rounds or validates first",[177,565,566,569,572,575,580],{},[201,567,568],{},"18",[201,570,571],{},"Rate limit",[201,573,574],{},"Send more than 40 requests in a minute",[201,576,577],{},[84,578,579],{},"429 development_instance_rate_limit_exceeded",[201,581,582,583],{},"Client backs off using ",[84,584,455],{},[177,586,587,590,593,600,606],{},[201,588,589],{},"19",[201,591,592],{},"Virtual account deposit",[201,594,595,596,599],{},"Create an account, then call ",[84,597,598],{},"simulate-spei-deposit"," on a SPEI CLABE account",[201,601,602,603,605],{},"New payin, ",[84,604,142],{},", mapped to the customer",[201,607,608,609],{},"Deposit tied to the right ",[84,610,611],{},"customer_id",[177,613,614,617,620,627,630],{},[201,615,616],{},"20",[201,618,619],{},"Daily reconciliation",[201,621,622,623,626],{},"List payins with ",[84,624,625],{},"limit",", compare with ledger",[201,628,629],{},"Zero unexplained differences",[201,631,632],{},"Exception queue empty or assigned",[54,634,635,636,640,641,70],{},"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 ",[61,637,639],{"href":638},"\u002Fresources\u002Fmore\u002Fpayin-webhooks-idempotency-reconciliation","payin webhooks, idempotency, and reconciliation",", and the failure behaviour is covered in ",[61,642,644],{"href":643},"\u002Fresources\u002Fmore\u002Ffailed-late-or-mismatched-stablecoin-payins","failed, late, or mismatched stablecoin payins",[72,646,648],{"id":647},"how-do-i-test-webhooks-locally","How do I test webhooks locally?",[54,650,651,652,655],{},"Expose your local handler on a public https URL, because BlindPay rejects local and private addresses and accepts only ",[84,653,654],{},"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.",[54,657,658],{},"Three habits make local testing reliable:",[660,661,662,665,668],"ol",{},[80,663,664],{},"Verify against the raw body. Parsing and re-serializing JSON changes the bytes and breaks the signature.",[80,666,667],{},"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.",[80,669,670],{},"Keep your test and production endpoints separate. The development webhook configuration does not carry over to production.",[54,672,673,674,70],{},"Webhooks behave identically in development and production: the same event names, payload shapes, and signatures. Signature rules are in the ",[61,675,677],{"href":676},"\u002Fdocs\u002Flearn\u002Fwebhooks","webhooks docs",[72,679,681],{"id":680},"what-should-i-monitor-at-launch","What should I monitor at launch?",[54,683,684],{},"Watch the state of payments, not only errors. In the first weeks, track:",[77,686,687,694,700,707,710,713],{},[80,688,689,690,693],{},"Webhook non-",[84,691,692],{},"2xx"," responses and signature failures",[80,695,696,697,699],{},"Payins in ",[84,698,157],{}," beyond the rail window (30 minutes for Pix, SPEI, Transfers, and PSE; 5 business days for ACH and wire)",[80,701,702,703,706],{},"Payins ",[84,704,705],{},"on_hold"," for more than 24 hours, because an unanswered request for information may end in a refund",[80,708,709],{},"Any terminal payin that changes state",[80,711,712],{},"Daily reconciliation exceptions",[80,714,715],{},"Share of quotes that expire before a payin is created",[54,717,718,719,87,722,725,726,729,730,733,734,736,737,739,740,87,743,746],{},"You can script these checks. The BlindPay CLI has ",[84,720,721],{},"blindpay payin_quotes create",[84,723,724],{},"blindpay payins create --quote-id qu_...",", with ",[84,727,728],{},"--json"," output and exit codes ",[84,731,732],{},"0"," (success), ",[84,735,203],{}," (user error), and ",[84,738,231],{}," (API error), so a CI job can fail on a bad code. Agents connected to the MCP server can read payins with ",[84,741,742],{},"GetV1InstancesPayins",[84,744,745],{},"GetV1InstancesPayinsById",". Point them at the read-only URL when they should only observe.",[72,748,750],{"id":749},"what-belongs-on-the-go-live-checklist","What belongs on the go-live checklist?",[54,752,753],{},"Copy this list into your release ticket.",[660,755,758,768,774,795,806,812,821,829,835,843],{"className":756},[757],"contains-task-list",[80,759,762,767],{"className":760},[761],"task-list-item",[763,764],"input",{"disabled":765,"type":766},true,"checkbox"," Production instance exists (it can take up to 3 business days to provision) and has its own API key.",[80,769,771,773],{"className":770},[761],[763,772],{"disabled":765,"type":766}," Real KYC documents are submitted for every customer; placeholder URLs removed.",[80,775,777,779,780,783,784,787,788,791,792,794],{"className":776},[761],[763,778],{"disabled":765,"type":766}," ",[84,781,782],{},"token"," changed from ",[84,785,786],{},"USDB"," to ",[84,789,790],{},"USDC"," or ",[84,793,534],{},", and networks changed from testnets to mainnets.",[80,796,798,800,801,87,803,805],{"className":797},[761],[763,799],{"disabled":765,"type":766}," Test amounts ",[84,802,86],{},[84,804,90],{}," removed from your code.",[80,807,809,811],{"className":808},[761],[763,810],{"disabled":765,"type":766}," A new webhook endpoint exists on the production instance, and its signing secret is stored.",[80,813,815,817,818,820],{"className":814},[761],[763,816],{"disabled":765,"type":766}," Signature check, ",[84,819,138],{}," dedupe, and forward-only status logic are tested (cases 4 to 8).",[80,822,824,779,826,828],{"className":823},[761],[763,825],{"disabled":765,"type":766},[84,827,407],{}," sent on every create call.",[80,830,832,834],{"className":831},[761],[763,833],{"disabled":765,"type":766}," Stuck-payin, on-hold, and reconciliation alerts have an owner.",[80,836,838,840,841,70],{"className":837},[761],[763,839],{"disabled":765,"type":766}," Payer screens show real bank details, not ",[84,842,219],{},[80,844,846,848],{"className":845},[761],[763,847],{"disabled":765,"type":766}," One small supervised payin per rail has completed in production.",[54,850,851,852,856],{},"For tokens and networks, ",[61,853,855],{"href":854},"\u002Fresources\u002Fmore\u002Fwhich-blockchain-network-for-payins","which blockchain network to use for payins"," helps with the choice.",[72,858,860],{"id":859},"where-does-blindpay-fit","Where does BlindPay fit?",[54,862,863,864,87,866,868,869,871,872,70],{},"BlindPay offers the pieces this checklist relies on: development instances with automatic KYC approval, a 30-second payin completion, forced outcomes at ",[84,865,86],{},[84,867,90],{},", a ",[84,870,472],{}," name for KYC rejection, identical webhook behavior across environments, a replayable events dashboard, and idempotent create calls. Details are in the ",[61,873,875],{"href":874},"\u002Fdocs\u002Flearn\u002Fsandbox-vs-production","sandbox vs production docs",[54,877,878,879,882,883,70],{},"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 ",[61,880,881],{"href":881},"\u002Fpricing","). To see how a vendor's tooling fits, start with ",[61,884,886],{"href":885},"\u002Fresources\u002Fmore\u002Fwhat-is-a-stablecoin-payin-api","what a stablecoin payin API is",[72,888,890],{"id":889},"sources","Sources",[77,892,893,900,905],{},[80,894,895,896,899],{},"BlindPay docs, ",[61,897,898],{"href":874},"Sandbox vs. production"," (checked on 2026-10-29)",[80,901,895,902,899],{},[61,903,904],{"href":676},"Webhooks",[80,906,895,907,899],{},[61,908,910],{"href":909},"\u002Fdocs\u002Fmcp\u002Ftools","BlindPay MCP tools reference",[54,912,913],{},[914,915,916],"em",{},"This article is for general information only and is not legal, tax, or financial advice.",{"title":918,"searchDepth":919,"depth":919,"links":920},"",2,[921,922,923,924,925,926,927,928],{"id":74,"depth":919,"text":75},{"id":112,"depth":919,"text":113},{"id":165,"depth":919,"text":166},{"id":647,"depth":919,"text":648},{"id":680,"depth":919,"text":681},{"id":749,"depth":919,"text":750},{"id":859,"depth":919,"text":860},{"id":889,"depth":919,"text":890},[930,935,940],{"path":931,"title":932,"description":933,"date":934},"\u002Fresources\u002Fmore\u002Fstablecoin-payin-api-pricing","Stablecoin 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.","2026-10-31",{"path":936,"title":937,"description":938,"date":939},"\u002Fresources\u002Fmore\u002Fhow-to-choose-a-stablecoin-payin-api","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.","2026-11-01",{"path":941,"title":942,"description":943,"date":7},"\u002Fresources\u002Fmore\u002Fhow-to-evaluate-a-global-checkout-provider","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.",1794399384086]