Going from sandbox to production

The sandbox runs the same lifecycle engine as production, with the external counterparties simulated. Your integration should therefore need very little to change, but the things that do change will fail loudly at the worst moment if you have not planned for them. This guide is the checklist.

What changes

SandboxProduction
Token endpointhttps://auth.sandbox.ibanq.com/oauth/tokenhttps://auth.ibanq.com/oauth/token
audiencehttps://api.sandbox.ibanq.comhttps://api.ibanq.com
API base URLhttps://api.sandbox.ibanq.comhttps://api.ibanq.com
CounterpartiesSimulated screening, processor and FX brokerReal
Outcome triggersreference prefixes select the outcomeNo special meaning
Simulation endpointsAvailable404
⚠️

audience is matched literally

The most common go-live failure is an audience that is nearly right. It is compared as a string against the registered API identifier, so a trailing slash or a leftover sandbox. gives 403 access_denied: "Service not enabled within domain". It is the API hostname, never the token hostname.

What does not change

Authentication, DPoP and request signing behave identically. The same three key types, the same PS256 client assertion, the same ES256 DPoP proofs, the same RFC 9421 signature base with the four covered components in the same order. If it works in the sandbox it works in production, provided you are pointing at the right hostnames with the right credentials.

Endpoint paths, request and response shapes, status codes, pagination and idempotency semantics are all the same.

Nothing you created in the sandbox exists in production

Every identifier is environment-specific. Your clientId, account ids, payee ids, quote ids and payment ids are all sandbox rows and none of them resolve in production.

This matters more than it sounds, because it means:

  • No id may be a constant in your code. If a GUID is compiled in rather than configured, it will 404 on the first production call.
  • Payees must be created again in production, and GB payees will need a fresh Confirmation of Payee check against the real scheme. A name that passed against the simulator can legitimately fail against the real bank.
  • Your accounts start with real balances rather than simulated ones. There is no POST /v1/simulations/inbound-payments in production to top them up.

Remove the sandbox scaffolding

Two things in your test fixtures will behave very differently once they are real.

The outcome triggers are the dangerous one. In the sandbox a reference beginning PAYMENT-rejected-bank or PAYMENT-held selects a simulated outcome. In production it is just text, and it is the text your beneficiary sees on their statement. Grep your codebase for the trigger prefixes before you ship. The same applies to TRADE-failed on FX internalReference and COP-rejected on accountHolderName.

The simulation endpoints are the other. POST /v1/simulations/inbound-payments and POST /v1/simulations/fx-trades/{id}/reject return 404 outside the sandbox, so any code path that reaches them needs to be behind an environment flag rather than discovered at runtime.

Expect real timings and real rejections

The sandbox is deterministic and fast. Production is neither.

FX quotes in the sandbox are priced from fixed rates and are executable for a fixed short window; in production the rate moves and the window is what the broker gives you. Do not assume a quote survives a user's coffee break. Handle the 422 on booking as a re-quote, not as an error.

Payments in the sandbox reach a terminal status quickly. In production a payment can sit at Processing for as long as the rail takes, and AwaitingFunds genuinely waits for money to arrive. Subscribe to webhooks through your account manager, or poll GET /v1/events; if you poll payments directly, back off rather than tight-loop. See Payment lifecycle and statuses.

Compliance screening rejects real payments for real reasons. Rejected and Returned are normal production outcomes, not bugs, and your reconciliation has to handle both.

Go-live checklist

  • Both hostnames and the audience come from configuration, and nothing has sandbox in it.
  • Production credentials are registered and you have confirmed with your account manager which applicationId, clientId, signing keyid and scopes apply. Do not assume the sandbox values carry over.
  • Private keys live in your production secret store, not in the repository, and you know how you will rotate them. Two authentication keys can be registered at once, which is what makes a zero-downtime rotation possible.
  • No sandbox identifier is hard-coded anywhere.
  • No outcome-trigger prefix survives in any reference, internal reference or account holder name.
  • Simulation endpoints are unreachable outside the sandbox environment.
  • Idempotency keys are generated and persisted before the request is sent, so a lost response is recoverable.
  • Your poller backs off and tolerates a payment sitting in a non-terminal status for hours.
  • You have decided what your system does on Rejected, Returned and AwaitingFunds.
  • Clocks are synchronised. created on a request signature must be within 60 seconds of server time, so NTP drift on a production host surfaces as intermittent 400s.

Ask your account manager before you switch

A few things are not answered by this documentation and are specific to your onboarding:

  • Which rails and currencies are enabled for your client in production. paymentMethod auto-resolution depends on it, and an explicit rail that is disabled is rejected.
  • Your production rate limits, so your poller and any bulk job are sized for them.
  • Payment cut-off times and value-date windows per rail.
  • Whether any approval or exposure limits apply to API-created payments.
  • Your trading exposure limit. Without one, POST /v1/fx/trades is rejected with 422.

A sensible first production call

Do not make your first production request a payment. Make it a read:

GET /v1/reference/currencies HTTP/1.1
Host: api.ibanq.com
Authorization: DPoP <access_token>
DPoP: <fresh_dpop_proof>

It needs no signing and moves no money, so it proves the token, the audience, the DPoP proof and the hostname in one call. Then GET /v1/accounts to confirm you are seeing the right client's data. Only then send a small real payment to an account you control.


Did this page help you?