Simulation endpoints
Five sandbox endpoints drive the test journeys. They use your normal token, AccountId header and collections permissions. They return 404 in production.
| Endpoint | Use it when | What it changes | What it does not do |
|---|---|---|---|
POST /v1/collections/simulate-payment | You want a quick success by bank transfer, card or crypto, without opening checkout | Creates a payment and moves the request to PAID or PART_PAID. Bank transfer: also completes the payment and credits the wallet at once, with the full webhook lifecycle. Card: completes the payment and sends the full lifecycle, but a simulated card payment does not add to the sandbox wallet balance. Crypto: credits the wallet and sends payment.completed only when the request reaches PAID | Use a real checkout quote. Mix payment methods on one request. Pay more than the remaining balance. |
POST /v1/collections/simulate-crypto-deposit | You want to test the real checkout quote: pending deposits, split deposits, quote timeouts | With PENDING: records a detected deposit only, no funds, no webhooks. With CONFIRMED: updates received funds and the request status, sends the confirmation webhooks | Credit the wallet. Send a blockchain transaction. Accept a new deposit on an expired quote. A deposit already detected can still be confirmed with its original depositId. |
POST /v1/collections/simulate-payment-completion | Confirmed deposits exist and you want the wallet credited | Simulates provider settlement, credits the wallet, sends payment.completed, creates a payout if the request has payout details | Complete a partly paid quote that is still open. Complete a rejected payment. Reopen an expired request. |
POST /v1/collections/simulate-expiry | You want a time limit to pass now | Moves one expiry clock into the past, then applies the normal expiry rules | Settle money. Extend a deadline. Reopen a request. |
POST /v1/collections/simulate-payout | A completed payment has a payout and you want to approve, reject, pay or fail it | Runs the production approval or bank-result path for that payout and sends its webhook | Create a payout. Skip a step, for example PAID while still awaiting approval. |
Expiry targets
target | Moves | Result |
|---|---|---|
CRYPTO_QUOTE | The quote's expiry | No new deposits on that quote. Nothing else changes. |
PAYMENT_DEADLINE | The point where new payments stop, 15 minutes before expiresAt | Checkout refuses new payments. Started payments can finish. |
PAYMENT_REQUEST | expiresAt | The request becomes EXPIRED and sends payment_request.expired, unless a payment is still confirming. |
PAYMENT_DEADLINE and PAYMENT_REQUEST need a HOSTED_PAGE or EMBEDDED request. A PAYMENT_LINK request never expires.
Payout outcomes
| Payout status | outcome | Becomes | Webhook |
|---|---|---|---|
PENDING_APPROVAL | APPROVED | INITIATED | payout.initiated |
PENDING_APPROVAL | REJECTED | FAILED | payout.failed |
INITIATED | PAID | PAID | payout.paid |
INITIATED | FAILED | FAILED | payout.failed |
Any other combination is refused with a 400 that names the current status and, when there is one, the outcome to send first. For example, PAID on a PENDING_APPROVAL payout tells you to send APPROVED first. PAID and FAILED are final: repeating the same outcome returns alreadyProcessed: true, and the other outcome is refused. Add failureReason to record why a payout was rejected or failed.
Choosing the crypto deposit amount
Fields in simulate-crypto-deposit | Result |
|---|---|
| No amount fields | Pays what is left on the quote |
"percentageOfRemaining": 50 | Pays half of what is left. More than 0, at most 100, up to 6 decimals. |
"cryptoAmount": 10.25 | Pays exactly 10.25 tokens of the quote's cryptocurrency |
Send a percentage or an exact amount, not both. "What is left" means the quote amount minus all pending and confirmed deposits on that quote.
Retrying safely
- Same
depositIdretries the same deposit, or moves it fromPENDINGtoCONFIRMED. The quote and amount do not change. A confirmed deposit never goes back to pending. - New
depositIdcreates a new deposit. It needs an open quote and anUNPAIDorPART_PAIDrequest. - Repeating a successful deposit call returns
alreadyProcessed: true. Repeating a successful completion call returnsalreadyCompleted: true. Repeating a payout outcome the payout already reached returnsalreadyProcessed: true. None of them adds money or sends webhooks again. - Repeating an expiry call never extends a deadline or reopens an expired request. It runs the expiry check again, so a request that stayed open earlier, for example while a deposit was pending, can expire on the retry. The response reports the current status and clocks.
- If a deposit, completion or expiry call times out, retry with the same IDs or read the current status. Do not invent a new
depositId. simulate-paymenthas no ID. If it times out, read the payment request first. Each call that succeeds creates a new payment.
Errors
| Code | Meaning | What to do |
|---|---|---|
400 | The request is valid JSON but the action is not allowed now: closed quote, amount too large, wrong expiry target, payment not ready to complete | Read the message. Fix the body, or do the earlier scenario step first. |
404 | Payment request, payment or quote not found for this account | Check the reference and the AccountId header. |
What you cannot simulate yet
| Result | Today | What to do instead |
|---|---|---|
payment.failed | No simulator | Nothing in sandbox. In production, record the failed attempt and read the request's current status. A failed card attempt does not cancel the request, and the payer can try again. |
| A payout on a simulated bank transfer | Payout is created and payout.pending_approval or payout.initiated is sent, but the payment does not show it and simulate-payout cannot find or approve it | Use CARD or crypto for payout tests. In production, bank transfer payouts work normally. |
payment_request.cancelled | No simulator | Nothing in sandbox. |
| Card declined or 3DS challenge | Not in the simulator | Use test card numbers on the hosted payment page. |
| Payer sends more than the amount | Rejected by the simulator | Nothing in sandbox. |
document.validation_warnings | No simulator | Nothing in sandbox. |
| Connection accepted, declined or revoked | No simulator | Nothing in sandbox. |
If you need one of these, tell your Hubpay contact.