Sandbox Outcome Triggers
How the sandbox decides outcomes
The sandbox runs the same lifecycle engine as production, with the external counterparties
(screening, the payment processor, the FX broker) simulated. Outcomes are chosen by
deterministic triggers: a reference that starts with a documented trigger token produces that
outcome; any other value takes the happy path.
Triggers are case-sensitive prefixes. Everything after the trigger is yours, so append your own
correlation id: PAYMENT-rejected-bank order-4711. Status transitions are asynchronous; poll the
read endpoint after creating.
1. Fund an account
Accounts start empty and outbound flows check the balance first, so fund the account by simulating
an inbound payment. No request signing on simulation endpoints.
POST /v1/simulations/inbound-payments
{
"clientId": "{clientId}",
"accountId": "{account id from GET /v1/accounts}",
"amount": 100000,
"currency": "GBP",
"paymentType": "Swift",
"reference": "Sandbox funding"
}paymentType accepts Fps, Chaps, Swift or Sepa (case-insensitive).
Returns 202 { "inboundPaymentId": "..." }; the balance has landed when GET /accounts shows it.
Amounts are minor units.
An inbound payment whose reference starts with PAYMENT-rejected-compliance is rejected by
screening instead: the 202 still returns the payment id, but no balance is ever credited.
2. Payments
Create payments as normal (POST /v1/outbound-payments, signed). The reference field picks
the outcome, observed via GET /v1/outbound-payments/{id}:
| reference starts with | status | what it simulates |
|---|---|---|
| anything else | Sent | The full happy path: screening accepted, submitted to the processor, executed by the bank. The account is debited. |
PAYMENT-rejected-compliance | Rejected | Compliance screening rejects the payment before it reaches the bank. No funds move. |
PAYMENT-rejected-bank | Rejected | The bank rejects the payment after submission. No funds move. |
PAYMENT-returned | Returned | The payment completes, then the bank returns it. |
PAYMENT-held | Processing | The bank holds the payment. It stays in Processing indefinitely. It becomes cancellable once the hold has been recorded, which can take up to a minute after creation; a cancel attempt before that returns 422. |
A payment with a future valueDate shows Scheduled and only enters the lifecycle on that date.
An unfunded same-day payment shows AwaitingFunds.
A payment with a future valueDate may read Processing for a few seconds after creation before it shows Scheduled.
3. FX trades
Quotes come from the sandbox broker at deterministic rates (GBPUSD 1.25, GBPEUR 1.15, EURUSD 1.10,
less your configured spread) and are executable for 10 seconds. Two failure modes:
Prerequisite. Booking a trade requires a trading exposure limit on your sandbox client. If
POST /v1/fx/tradesreturns422 Trading exposure limit is not configured for this client, ask your account manager to set one. Quotes work without it.
| how | status | what it simulates |
|---|---|---|
internalReference starts with TRADE-failed on POST /fx/trades | Failed | The order never reaches the broker. failedReason: "The trade could not be sent to the broker." |
POST /v1/simulations/fx-trades/{id}/reject on a booked trade | Failed | The broker rejects the booked order; returns 204. failedReason: "The broker rejected the trade." 404 for another client's trade or outside the sandbox; 422 if the trade is no longer booked. |
Any other trade books and rests at Booked; no broker fill follows in the sandbox.
4. Payee name checks
Confirmation of Payee is simulated through the accountHolderName on
POST /v1/account-name-verifications:
answer is one of Match, CloseMatch, NoMatch, Unavailable. A rejection only counts as NoMatch when a name comparison actually ran; an account-level reason such as AC01, or a delivery failure carrying a statusReason, reads as Unavailable. Only an AC01 outcome blocks POST /v1/payees.
| accountHolderName starts with | answer | also returns | usable for payee creation |
|---|---|---|---|
| anything else | Match | actualName echoes the requested name | Yes |
COP-rejected | Unavailable | reasonCode: AC01 | No |
COP-rejected-AC01 | Unavailable | reasonCode: AC01 | No |
COP-rejected-with-status-reason | Unavailable | statusReason: responder_unavailable | Yes |
COP-warning-PAMM | CloseMatch | reasonCode: PAMM | Yes |
End-to-end walkthrough
- Fund:
POST /simulations/inbound-paymentsfor 100000 GBP, thenGET /accountsshows the balance. - Verify and create a payee:
POST /account-name-verifications, thenPOST /payees. - Happy payment:
POST /outbound-paymentswith a plain reference, pollGET /outbound-payments/{id}untilSent; the balance drops. - Failed payment: repeat with reference
PAYMENT-rejected-bank test-1, observeRejected, balance unchanged. - FX:
POST /fx/quotes, thenPOST /fx/tradeswithinternalReference: TRADE-failed-1, andGET /fx/trades/{id}showsFailed.
Notes
- Authentication, DPoP and request signing behave exactly as in production; simulation endpoints skip request signing only.
- The simulation endpoints appear in the generated OpenAPI spec with a sandbox-only note in every code sample; this page adds the trigger vocabulary, which lives outside the spec.
Updated 10 days ago