Skip to main content

Flow of Funds

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

Pre-requisites

Before you begin, ensure you have:
  1. A Superbank Developer account with API access
  2. A prefunded account with sufficient USDC balance
  3. Your API key.
Click here to learn how

On-Ramping

Step 1: Receive an instant on-ramping request

Your user sends a real-time on-ramping request via your UI, or API. Persist their destination wallet address (destination.wallet_address).

Step 2: Create a Settlement Request

Create a settlement request by sending a POST request to /v0/settlement-requests endpoint on Superbank’s API.
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.
We recommend supplying an external_id (your own identifier for this settlement) and optional metadata (free-form key/value pairs) on create. Both are echoed back on every read and webhook, and external_id is filterable on the list endpoint — meaning you don’t need to persist anything from the create response (not Superbank’s id, not payment_instructions). Look the settlement up by external_id whenever you need it; the full record (including payment_instructions) comes back on every lookup.

Request

cURL
curl --request POST \
  --url https://api-sandbox.superbank.co/v0/settlement-requests \
  --header 'Content-Type: application/json' \
  --header 'X-Api-Key: YOUR_API_KEY' \
  --data '{
    "type": "STABLECOIN_TO_STABLECOIN",
    "payment_reason": "REMITTANCES",
    "amount": 20,
    "external_id": "txn_abc123",
    "metadata": {
      "user_id": "usr_42",
      "source": "mobile_app"
    },
    // Optional. Override only when your processor's settlement window
    // differs from the T+3 default. ISO 8601, must be in the future, max 30 days out.
    "reconciliation_expected_at": "2026-05-04T17:00:00Z",
    "destination": {
      "currency": "USDC",
      "rail": "SOLANA",
      "is_third_party": true,
      "wallet_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
      "beneficiary": {
        "type": "BUSINESS",
        "address": {
          "country_code": "US"
        },
        "business_name": "Acme Corp"
      }
    }
  }'
Picking the right payment_reason. The enum is broad and is used for categorisation and reporting, not customer-visible metadata. For typical fintech flows the values you’ll actually use are: REMITTANCES (cross-border consumer transfers), PAYMENT_FOR_GOODS_AND_SERVICES (merchant payments), EMPLOYEE_SALARIES_OR_WAGES (payroll). Pick the closest specific value rather than reaching for OTHER.
destination.is_third_party — pick what’s true, not what’s convenient. Set true when the beneficiary is not your own registered company (e.g., a payout to a vendor, contractor, or end-user). Set false only when the destination is your own company / registered account.

Response

Some fields (e.g. outbound_payment, inbound_payment, timestamps, failure fields) are omitted in the examples on this page for brevity — see the Settlement Request reference for the full schema.
{
  "id": "39760060-846e-4d5a-8583-7ee62553f79b",
  "type": "STABLECOIN_TO_STABLECOIN",
  "payment_reason": "REMITTANCES",
  "status": "REQUEST_STARTED",
  "amount": "20.00000000",
  "external_id": "txn_abc123",
  "metadata": {
    "user_id": "usr_42",
    "source": "mobile_app"
  },
  "source": null,
  "destination": {
    "currency": "USDC",
    "rail": "SOLANA",
    "is_third_party": true,
    "wallet_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "beneficiary": {
      "type": "BUSINESS",
      "business_name": "Acme Corp",
      "address": {
        "country_code": "US"
      }
    }
  },
  "payment_instructions": {
    "amount": "20.00000000",
    "currency": "USDC",
    "rail": "SOLANA",
    "wallet_address": "AWE1XaAdRuxzjqy8Q7q75MFbPTs1W6Zbp3zvYWcDGjTj",
    "chain": "SOLANA",
    "valid_until": "2026-01-26T15:50:10.074Z"
  },
  "outbound_payment": null,
  "inbound_payment": null,
  "created_at": "2026-01-26T15:45:10.047Z",
  "updated_at": "2026-01-26T15:45:10.047Z",
  "processing_at": null,
  "completed_at": null,
  "reconciliation_expected_at": null
}
You don’t need to persist anything from this response - not Superbank’s id, and not payment_instructions. Both come back on the lookup. Look the settlement up later via the external_id filter whenever the next step runs:
cURL
curl 'https://api-sandbox.superbank.co/v0/settlement-requests?external_id=txn_abc123' \
  --header 'X-Api-Key: YOUR_API_KEY'
{
  "data": [
    {
      "id": "39760060-846e-4d5a-8583-7ee62553f79b",
      "external_id": "txn_abc123",
      "metadata": { "user_id": "usr_42", "source": "mobile_app" },
      "status": "REQUEST_STARTED",
      "payment_instructions": {
        "amount": "20.00000000",
        "currency": "USDC",
        "rail": "SOLANA",
        "wallet_address": "AWE1XaAdRuxzjqy8Q7q75MFbPTs1W6Zbp3zvYWcDGjTj",
        "valid_until": "2026-01-26T15:50:10.074Z"
      },
      "...": "..."
    }
  ],
  "meta": { "page": 1, "limit": 20, "total": 1, "total_pages": 1 }
}
external_id is not enforced as unique on Superbank’s side. If you reuse the same value (intentionally or by mistake), the response can include multiple settlements. If you need to disambiguate, filter the results client-side using metadata.

Step 3: Start on-ramping with your infrastructure provider

Use thepayment_instructions returned in the previous step as the on-ramping destination to request the deposit instructions from your infrastructure provider. For example, here’s how to do it with Bridge as your infrastructure provider.

Request

cURL
curl --location --request POST 'https://api.bridge.xyz/v0/transfers' \
--header 'Api-Key: <API Key>' \
--header 'Idempotency-Key: <Unique Idempotency Key>' \
--data-raw '{
  "amount": "1.0",
  "source": {
    "payment_rail": "ach_push",
    "currency": "usd",
  },
  "destination": {
    "payment_rail": "solana",
    "currency": "usdc",
    "to_address": "AWE1XaAdRuxzjqy8Q7q75MFbPTs1W6Zbp3zvYWcDGjTj",
  },
}'

Response

{
  "id": "transfer_123",
  "state": "awaiting_funds",
  "amount": "1.0",
  "source": {
    "payment_rail": "ach_push",
    "currency": "usd"
  },
  "destination": {
    "payment_rail": "solana",
    "currency": "usdc",
    "to_address": "AWE1XaAdRuxzjqy8Q7q75MFbPTs1W6Zbp3zvYWcDGjTj"
  },
  "source_deposit_instructions": {
    "bank_account_number": "123456789",
    "bank_routing_number": "101019644",
    "amount": "1.0",
    "currency": "usd",
    "deposit_message": "BVI7depositmessage"
  },
  "receipt": {
    "initial_amount": "1.0",
    "exchange_fee": "0.0",
    "final_amount": "1.0",
    "destination_tx_hash": "0xc0ffee"
  },
  "created_at": "2023-05-05T19:39:14.316Z",
  "updated_at": "2023-05-05T19:39:15.231Z"
}

Step 4: Update settlement request status to FUNDS_SENT

Once the End-User has initated the payment using your infrastructure provider, update the settlement request status to FUNDS_SENT by sending the PUT request to /v0/settlement-requests/:id. This triggers the instant settlement from your pre-funded wallet, to your User’s destination wallet.

Request

cURL
curl --request PUT \
  --url https://api-sandbox.superbank.co/v0/settlement-requests/39760060-846e-4d5a-8583-7ee62553f79b \
  --header 'Content-Type: application/json' \
  --header 'X-Api-Key: YOUR_API_KEY' \
  --data '{
    "status": "FUNDS_SENT"
  }'

Response

{
  "id": "39760060-846e-4d5a-8583-7ee62553f79b",
  "type": "STABLECOIN_TO_STABLECOIN",
  "payment_reason": "REMITTANCES",
  "status": "FUNDS_SENT",
  "amount": "20.00000000",
  "external_id": "txn_abc123",
  "metadata": {
    "user_id": "usr_42",
    "source": "mobile_app"
  },
  "source": null,
  "destination": {
    "currency": "USDC",
    "rail": "SOLANA",
    "is_third_party": true,
    "wallet_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "beneficiary": {
      "type": "BUSINESS",
      "business_name": "Acme Corp",
      "address": {
        "country_code": "US"
      }
    }
  },
  "payment_instructions": {
    "amount": "20.00000000",
    "currency": "USDC",
    "rail": "SOLANA",
    "wallet_address": "AWE1XaAdRuxzjqy8Q7q75MFbPTs1W6Zbp3zvYWcDGjTj",
    "chain": "SOLANA",
    "valid_until": "2026-01-26T15:50:10.074Z"
  },
  "outbound_payment": {
    "id": "2a9d2a2c-d97b-4973-baf1-78e31d37a024",
    "type": "PAYOUT",
    "status": "PROCESSING",
    "amount": "20.00000000",
    "currency": "USDC",
    "created_at": "2026-01-26T15:45:51.589Z"
  },
  "inbound_payment": null,
  "created_at": "2026-01-26T15:45:10.047Z",
  "updated_at": "2026-01-26T15:45:51.589Z",
  "processing_at": "2026-01-26T15:45:51.594Z",
  "completed_at": null,
  "reconciliation_expected_at": "2026-01-29T15:45:51.594Z"
}

Step 5: Instant settlement completed

Once you confirm the funds are sent via Step 4, Superbank moves USDC from your pre-funded wallet to the End-User’s wallet in real time. The End-User receives their USDC instantly (blockchain speed).

Detecting Completion

Between Step 5 (Superbank moves the funds) and Step 6 (you call REQUEST_COMPLETED with the transaction hash), your system needs to know that the settlement is done. There are two ways to find out, and we strongly recommend the first. Subscribe a webhook endpoint and listen for settlement_request.updated events where data.status === 'SETTLEMENT_COMPLETED'. As soon as Superbank moves the funds, you’ll get a signed POST to your endpoint and can immediately fire Step 6.
{
  "event": "settlement_request.updated",
  "data": {
    "id": "39760060-846e-4d5a-8583-7ee62553f79b",
    "status": "SETTLEMENT_COMPLETED",
    "previous_status": "FUNDS_SENT",
    "amount": "20.00000000",
    "external_id": "txn_abc123",
    "metadata": { "user_id": "usr_42", "source": "mobile_app" },
    "outbound_payment_id": "2a9d2a2c-d97b-4973-baf1-78e31d37a024",
    "updated_at": "2026-01-26T15:48:08.670Z"
  },
  "timestamp": "2026-01-26T15:48:08.700Z"
}
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 === 'SETTLEMENT_COMPLETED'. Recommended interval: 5 seconds or longer. Polling more aggressively wastes your rate-limit budget without speeding up settlement (the underlying blockchain confirmation is the gating factor, not our API).
cURL
curl https://api-sandbox.superbank.co/v0/settlement-requests/39760060-846e-4d5a-8583-7ee62553f79b \
  --header 'X-Api-Key: YOUR_API_KEY'
Webhooks are the better choice in almost every case: lower latency, no wasted requests, and you don’t have to keep state about what you’re polling for.

Step 6: Reconcile via REQUEST_COMPLETED

After T+X — when you’ve collected the fiat from the end user and reconciled it on-chain into your Superbank prefunded wallet — close the settlement by calling REQUEST_COMPLETED with the transaction hash of the on-chain reconciliation. The on-chain reconciliation typically happens one of two ways: (a) Direct deposit via payment_instructions. Use the wallet address or bank details shown in payment_instructions from Step 2’s response. Funds deposited there settle automatically into the prefunded wallet, so you don’t need to perform a separate forwarding step. (b) Third-party forwarding. If you receive the funds on a third-party infrastructure first (e.g. Bridge, Coinbase, your own off-ramp), forward the resulting stablecoin on-chain to the Superbank prefunded wallet to reconcile. In both cases, the transaction_hash in the request below is the hash of the on-chain transfer that lands stablecoin in the prefunded wallet — not the destination payment Superbank made to the end user (that’s the OUTBOUND, which auto-confirms in seconds and doesn’t need to be reported back).
This step is crucial. Without the on-chain transaction hash from the reconciliation step, Superbank cannot match the settlement to the inbound funds.

Request

cURL
curl --request PUT \
  --url https://api-sandbox.superbank.co/v0/settlement-requests/39760060-846e-4d5a-8583-7ee62553f79b \
  --header 'Content-Type: application/json' \
  --header 'X-Api-Key: YOUR_API_KEY' \
  --data '{
    "status": "REQUEST_COMPLETED",
    "transaction_hash": "3CBCEbF2yikAza5d8pcxpibrR4hkj78wAZPYGPtXokNsxxUqPG29aSkrNW6nXnvWzjDePgzYgcLa4G9rJ78k8cXo"
  }'

Response — two possible outcomes

The status in the response depends on whether Superbank has already detected the inbound on-chain transfer to your prefunded wallet at the moment you submit this call:
  • REQUEST_COMPLETED — the inbound PAYIN was already detected and the amount matched. The settlement is final; completed_at is set and inbound_payment is populated. This is the typical path when you call REQUEST_COMPLETED after the on-chain transfer has had time to confirm and be picked up by Superbank’s indexer.
  • AWAITING_PAYIN_RECONCILIATION — your transaction_hash is recorded, but the matching inbound PAYIN has not yet been observed. Superbank will defer the amount check until the PAYIN lands. As soon as it does, the request automatically resolves to REQUEST_COMPLETED (or PAYIN_AMOUNT_MISMATCH if the received amount differs from the settlement amount) and Superbank emits another settlement_request.updated webhook. No further action is required from your side. This is most commonly seen when you call REQUEST_COMPLETED immediately after broadcasting the on-chain transfer, before it has confirmed and been indexed, or on rails with slower finality.
REQUEST_COMPLETED (synchronous path)
{
  "id": "39760060-846e-4d5a-8583-7ee62553f79b",
  "type": "STABLECOIN_TO_STABLECOIN",
  "payment_reason": "REMITTANCES",
  "status": "REQUEST_COMPLETED",
  "amount": "20.00000000",
  "external_id": "txn_abc123",
  "metadata": {
    "user_id": "usr_42",
    "source": "mobile_app"
  },
  "source": null,
  "destination": {
    "currency": "USDC",
    "rail": "SOLANA",
    "is_third_party": true,
    "wallet_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "beneficiary": {
      "type": "BUSINESS",
      "business_name": "Acme Corp",
      "address": {
        "country_code": "US"
      }
    }
  },
  "payment_instructions": {
    "amount": "20.00000000",
    "currency": "USDC",
    "rail": "SOLANA",
    "wallet_address": "AWE1XaAdRuxzjqy8Q7q75MFbPTs1W6Zbp3zvYWcDGjTj",
    "chain": "SOLANA",
    "valid_until": "2026-01-26T15:50:10.074Z"
  },
  "outbound_payment": {
    "id": "2a9d2a2c-d97b-4973-baf1-78e31d37a024",
    "type": "PAYOUT",
    "status": "COMPLETED",
    "amount": "20.00000000",
    "currency": "USDC",
    "created_at": "2026-01-26T15:45:51.589Z"
  },
  "inbound_payment": {
    "id": "04621f85-bd40-46a9-a9a9-9fe14be09354",
    "type": "PAYIN",
    "status": "COMPLETED",
    "amount": "20.00000000",
    "currency": "USDC",
    "created_at": "2026-01-26T14:12:08.354Z"
  },
  "created_at": "2026-01-26T15:45:10.047Z",
  "updated_at": "2026-01-26T15:48:08.670Z",
  "processing_at": "2026-01-26T15:45:51.594Z",
  "completed_at": "2026-01-26T15:48:08.657Z",
  "reconciliation_expected_at": "2026-01-29T15:45:51.594Z"
}
AWAITING_PAYIN_RECONCILIATION (deferred path) Same request, but the inbound PAYIN has not yet been indexed. Note the absent inbound_payment and completed_at — both will be populated when Superbank later detects the on-chain transfer and finalises the request.
{
  "id": "39760060-846e-4d5a-8583-7ee62553f79b",
  "type": "STABLECOIN_TO_STABLECOIN",
  "payment_reason": "REMITTANCES",
  "status": "AWAITING_PAYIN_RECONCILIATION",
  "amount": "20.00000000",
  "external_id": "txn_abc123",
  "metadata": {
    "user_id": "usr_42",
    "source": "mobile_app"
  },
  "outbound_payment": {
    "id": "2a9d2a2c-d97b-4973-baf1-78e31d37a024",
    "type": "PAYOUT",
    "status": "COMPLETED",
    "amount": "20.00000000",
    "currency": "USDC",
    "created_at": "2026-01-26T15:45:51.589Z"
  },
  "inbound_payment": null,
  "created_at": "2026-01-26T15:45:10.047Z",
  "updated_at": "2026-01-26T15:46:12.110Z",
  "processing_at": "2026-01-26T15:45:51.594Z",
  "completed_at": null,
  "reconciliation_expected_at": "2026-01-29T15:45:51.594Z"
}
What to do when you receive AWAITING_PAYIN_RECONCILIATION. Treat it as a successful acknowledgement — do not retry the REQUEST_COMPLETED call. Wait for the follow-up settlement_request.updated webhook (or poll the settlement) to see the request transition to REQUEST_COMPLETED once the inbound PAYIN lands and matches.

Next Steps

Webhooks

Wire up a webhook handler to detect SETTLEMENT_COMPLETED without polling — envelope, headers, signature verification, and the full event list.

Fiat → Stablecoin on-ramping

Same destination shape, but the end-user deposits fiat instead of stablecoin. Use this when there’s a fiat leg on the source side and you want Superbank to handle the conversion and deposit instructions.