Flow of Funds
Refresher on how instant on-ramping works with Superbank’s API.
When to use this guide
UseFIAT_TO_STABLECOIN when the end-user is depositing fiat (e.g.
USD via ACH) and you want to deliver stablecoin (e.g. USDC on Solana)
to their wallet. Superbank handles the fiat → stablecoin conversion and
returns ready-to-render deposit instructions (bank name, routing number,
account number, deposit message) on the response.
If the end-user is depositing stablecoin (not fiat), use
Real Time On-Ramping
(STABLECOIN_TO_STABLECOIN) instead.
Pre-requisites
Before you begin, ensure you have:- A Superbank Developer account with API access
- Your API key
- The end-user’s destination wallet address (chain + address)
- Optional but recommended — a prefunded USDC balance large enough
to cover typical settlement sizes (see “How
prefundedis decided” below)
How prefunded is decided
FIAT_TO_STABLECOIN has two settlement branches that share the same
request shape but differ in who delivers stablecoin to the end-user.
Superbank picks the branch automatically based on your available
prefunded liquidity at the moment you create the request:
| Branch | Picked when | Who pays the end-user | FUNDS_SENT required |
|---|---|---|---|
prefunded: true | Your prefunded balance ≥ amount | You — Superbank fronts the stablecoin from your prefunded wallet the moment you call FUNDS_SENT. | Yes |
prefunded: false | Prefunded balance insufficient | Superbank — stablecoin is delivered straight to the end-user wallet once the fiat deposit is received. | No — webhook-driven |
prefunded: true|false). Both branches return identical deposit instructions for the
end-user — the only thing that changes is whether you call FUNDS_SENT.
Fiat → Stablecoin On-Ramping
Step 1: Receive an on-ramping request
Your end-user requests to on-ramp through your UI or API. They specify the destination wallet address (chain + address) and the stablecoin amount they want to receive. Persist the destination wallet address — you’ll send it on the next step.Step 2: Create the settlement request
Create a settlement request by sending aPOST to
/v0/settlement-requests. The response carries:
- The
prefundedflag (trueorfalse) describing which branch Superbank picked. - A
payment_instructionsblock carrying the locked quote (exchange_rate,fee) plus the source-side deposit slip — theamount/currency/railthe end-user must deposit, the receiving bank (bank_name,bic_swift,bank_address), the account (account_number,routing_number), the reference (deposit_message), and the beneficiary (account_holder_name,account_holder_address). All flat onpayment_instructions— same shape as off-ramp’spayment_instructions.
external_id (your internal identifier) and
optional metadata — both are echoed back on every read and webhook,
and external_id is filterable on the list endpoint. You don’t need to
persist anything from the create response.
Request — USD via ACH (US)
cURL
Request — NGN via NIBSS (Nigeria)
Same shape, different fiat corridor. The response carries NGN-side deposit instructions and applies the live NGN → USDC exchange rate locked at quote time.cURL
Response — prefunded: true (NGN → USDC)
Cross-currency example so the locked-quote fields aren’t trivial. The
end-user is depositing 150,750.00 NGN in Lagos via NIBSS and the
end-user wallet receives 100 USDC on Solana. The 750 NGN spread on
top of the 150,000 NGN mid-market amount is our 50 bps variable
fee. Some fields are omitted for brevity — see the
Settlement Request reference
for the full schema.
Response — prefunded: false
Identical shape — only prefunded flips to false. The deposit slip
fields are the same; the lifecycle differs from Step 4 onwards.
Step 3: Render the deposit slip and collect the fiat
Read the source-side fields offpayment_instructions and render them
as a bank-transfer instruction screen. The fields that matter for the
user:
amount+currency(onpayment_instructions) — the exact amount they must send (includes the conversion fee, in source currency).bank_name/account_number/routing_number— the receiving bank.bic_swiftfor international wires.account_holder_name— the beneficiary the user enters on their bank’s transfer form.deposit_message— the memo / reference field. This is how the deposit is matched back to your settlement; if it’s omitted the deposit may be returned.
Step 4 (prefunded: true only): Call FUNDS_SENT
Once the end-user confirms they’ve sent the fiat (or you’ve otherwise
verified the deposit is in flight on your side), call FUNDS_SENT to
trigger the instant stablecoin payout from your prefunded wallet to the
end-user’s wallet.
Request
cURL
Response
Step 5: Settlement completes
What happens next depends on the branch:prefunded: true— the stablecoin payout lands in the end-user wallet within seconds (SETTLEMENT_COMPLETED). When the fiat deposit clears on your side, Superbank automatically reconciles into your prefunded wallet and the settlement transitions toREQUEST_COMPLETED. You don’t need to callREQUEST_COMPLETEDmanually — the webhook fires when reconciliation completes.prefunded: false— Superbank waits for fiat clearing, then ships the stablecoin straight to the end-user wallet. The settlement transitionsREQUEST_STARTED → FUNDS_SENT → SETTLEMENT_COMPLETED → REQUEST_COMPLETEDend-to-end without any further calls from your side.
Detecting Completion
Subscribe a webhook endpoint and listen forsettlement_request.updated
events. The status you watch for depends on the branch:
prefunded: true—SETTLEMENT_COMPLETEDconfirms the end-user has their stablecoin. A second event withREQUEST_COMPLETEDlands later when fiat reconciliation closes.prefunded: false—REQUEST_COMPLETEDis the single terminal event. There is no intermediateFUNDS_SENTbecause Superbank drives the lifecycle without dev input.
X-Superbank-Event and X-Superbank-Signature
headers, and the complete list of event types are documented in the
Webhooks guide.
Polling (fallback)
If you can’t accept webhooks, pollGET /v0/settlement-requests/:id
until status === 'REQUEST_COMPLETED'. Recommended interval: 5
seconds or longer.
cURL
Failure modes
| Status | When it happens | What to do |
|---|---|---|
REQUEST_EXPIRED | End-user didn’t deposit before payment_instructions.valid_until passed. | Create a new settlement and re-render the deposit slip; the previous deposit_message is dead. |
PAYIN_AMOUNT_MISMATCH | Fiat deposit amount differs from payment_instructions.amount (rare — usually a fee shortfall). | Inspect failure_reason for the observed vs. expected amount. Refund or top-up as your KYC/operations policy dictates. |
PAYIN_FAILED | Fiat deposit rejected by the receiving bank (e.g. closed account, KYC bounce). | Surface the error to the end-user; create a new settlement once they can retry. |
PAYOUT_FAILED | Stablecoin payout to the end-user wallet failed (e.g. invalid address, chain congestion). | Inspect failure_reason. For prefunded: true, your prefunded balance is released automatically; create a new settlement to retry. |
Next Steps
Webhooks
Wire up a webhook handler to detect
SETTLEMENT_COMPLETED /
REQUEST_COMPLETED without polling — envelope, headers, signature
verification, and the full event list.Stablecoin → Stablecoin on-ramping
Same destination shape, but the end-user deposits stablecoin instead
of fiat. Use this when there’s no fiat leg on the source side.