Skip to main content

Flow of Funds

Refresher on how instant on-ramping works with Superbank’s API.

When to use this guide

Use FIAT_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:
  1. A Superbank Developer account with API access
  2. Your API key
  3. The end-user’s destination wallet address (chain + address)
  4. Optional but recommended — a prefunded USDC balance large enough to cover typical settlement sizes (see “How prefunded is decided” below)
Click here to learn how

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: You’ll see which branch was chosen on the create response (prefunded: true|false). Both branches return identical deposit instructions for the end-user — the only thing that changes is whether you call FUNDS_SENT.
prefunded: true is the faster experience. Stablecoin reaches the end-user wallet within seconds of your FUNDS_SENT call (you’ve already collected the fiat-receipt confirmation on your side). The prefunded: false branch waits for fiat clearing before the stablecoin ships, so it’s bounded by the fiat rail’s settlement window (e.g. ACH same-day for typical US flows).

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

Send an Idempotency-Key header. Recommended today, required in a future API version. Generate a UUID v4 per logical operation and include Idempotency-Key: <uuid> on the POST. Same key replayed → we return the original response, no duplicate settlement. See the Idempotency guide for retention, scope, error codes, and dispatcher pattern.
Create a settlement request by sending a POST to /v0/settlement-requests. The response carries:
  • The prefunded flag (true or false) describing which branch Superbank picked.
  • A payment_instructions block carrying the locked quote (exchange_rate, fee) plus the source-side deposit slip — the amount / currency / rail the 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 on payment_instructions — same shape as off-ramp’s payment_instructions.
We recommend supplying 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.
Top-level amount is the destination stablecoin amount. For FIAT_TO_STABLECOIN, set the top-level amount to the stablecoin the end-user should receive. The quote engine computes the fiat amount they need to deposit (payment_instructions.amount) — that’s the value you display to the end-user, not what you sent in.
source is required. Specify the fiat side via source: { currency, rail, country_code }. No bank account details — Superbank generates the receiving deposit instructions for you. The rail is a Walapay routing token — use LOCAL for any country-routed local scheme (SEPA, Faster Payments, SPEI, NIBSS, Interac, …); the country_code selects the scheme. Common pairs: USD + ACH + US, EUR + LOCAL + DE, NGN + LOCAL + NG, MXN + LOCAL + MX, CAD + LOCAL + CA.

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
destination.is_third_party. Identifies whether the beneficiary is your own business or someone else. Set false when the destination account is held by your own company, otherwise true.

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.
exchange_rate is destination per unit of source. For NGN → USDC that’s 0.0006666667 (≈ 1,500 NGN per 1 USDC). Same-currency corridors (e.g. USD → USDC) return 1.0000000000. The variable fee (fee.variable) is applied on top of the conversion — it is NOT bundled into the rate. So payment_instructions.amount = top-level amount / exchange_rate × (1 + fee.variable) + fee.fixed.
fee.variable is a fraction (bps / 10,000). In this example we quote 50 bps = 0.0050 = 0.5 %. Typical fiat corridors we run price in the 50 bps range; cross-currency corridors with FX exposure may price higher.
fee.fixed is in source currency and may be 0. The shape stays the same as off-ramping so you write one parser. When we add a flat per-deposit fee on a corridor it surfaces here; today, most corridors carry only a variable fee and fixed is 0.

Response — prefunded: false

Identical shape — only prefunded flips to false. The deposit slip fields are the same; the lifecycle differs from Step 4 onwards.
The deposit slip is short-lived. payment_instructions.valid_until is 5 minutes by default. After it expires the settlement transitions to REQUEST_EXPIRED and a new request must be created — the deposit_message cannot be reused.

Step 3: Render the deposit slip and collect the fiat

Read the source-side fields off payment_instructions and render them as a bank-transfer instruction screen. The fields that matter for the user:
  • amount + currency (on payment_instructions) — the exact amount they must send (includes the conversion fee, in source currency).
  • bank_name / account_number / routing_number — the receiving bank. bic_swift for 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.
deposit_message is mandatory and unique to this settlement. The end-user must include it in the bank-transfer reference / memo field. Without it, the deposit cannot be matched and will 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.
This step is prefunded: true only. On prefunded: false, Superbank ships the stablecoin automatically once fiat clears — you don’t call FUNDS_SENT and you’ll get a 400 if you try. Branch on the prefunded flag from Step 2’s response.

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 to REQUEST_COMPLETED. You don’t need to call REQUEST_COMPLETED manually — 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 transitions REQUEST_STARTED → FUNDS_SENT → SETTLEMENT_COMPLETED → REQUEST_COMPLETED end-to-end without any further calls from your side.

Detecting Completion

Subscribe a webhook endpoint and listen for settlement_request.updated events. The status you watch for depends on the branch:
  • prefunded: trueSETTLEMENT_COMPLETED confirms the end-user has their stablecoin. A second event with REQUEST_COMPLETED lands later when fiat reconciliation closes.
  • prefunded: falseREQUEST_COMPLETED is the single terminal event. There is no intermediate FUNDS_SENT because Superbank drives the lifecycle without dev input.
The full envelope, the 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, poll GET /v0/settlement-requests/:id until status === 'REQUEST_COMPLETED'. Recommended interval: 5 seconds or longer.
cURL

Failure modes

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.