> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bridge.linkio.world/llms.txt
> Use this file to discover all available pages before exploring further.

# Onramp: fiat to stablecoin

> Your customer pays in fiat and receives USDC.

In an onramp, your customer pays fiat and receives stablecoins in their stablecoin wallet.

```mermaid theme={null}
sequenceDiagram
  participant You as Your app
  participant BC as Bridge
  participant C as Your customer
  You->>BC: Get a quote
  BC-->>You: Quote (qte_...)
  You->>BC: Create onramp (quote + wallet)
  BC-->>You: Payment (pay_...) + deposit instructions
  You->>C: Show where to pay
  C->>BC: Pays fiat
  BC-->>You: Webhooks at each step
  BC->>C: Sends stablecoin to wallet
  BC-->>You: payment.onramp.completed
```

## Before you start

You need a [customer](https://docs.bridge.linkio.world/flows/onboard-customer.md) and an onramp [quote](https://docs.bridge.linkio.world/flows/quotes.md) that hasn't expired.

## Step 1: Create the onramp

Send the quote and the wallet that should receive the stablecoin.

```bash theme={null}
curl -X POST https://sandbox.core.linkio.world/api/payments/onramp \
  -H "X-BRIDGE-API-Key: $BRIDGE_API_KEY" \
  -H "API-Version: 2026-10-29-atlas" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1001" \
  -d '{
    "customer_id": "cus_4hT9pL2mQx8VnR3tY7wZ1cB5dE",
    "quote_id": "qte_9xLp3VnR7tY1wZ4cB6dE8fK2mQ",
    "payment_details": {
      "type": "stablecoin",
      "currency": "USDC",
      "details": {
        "address": "6KhMtrKUhYGqVVgV6fcr1Vr1XnQtZCrDwpXTNZHPiReR",
        "network": "solana"
      }
    }
  }'
```

| Field | What it means |
| - | - |
| `customer_id` | Must be the same customer as on the quote. |
| `quote_id` | The quote to use. Each quote can only be used once. |
| `payment_details.type` | Always `stablecoin` for an onramp. |
| `payment_details.currency` | Must match the quote's `target_currency`. |
| `payment_details.details.address` | The wallet that receives the stablecoin. |
| `payment_details.details.network` | Must match the quote's `network`. |
| `payer` | **Mobile money only.** The mobile money wallet that pays. See [Mobile money](https://docs.bridge.linkio.world/flows/mobile-money.md). |

> [!NOTE]
>
> Onramps are always first party: the customer pays for themselves and receives the stablecoin in their own wallet. Someone else can't pay on their behalf.

## Step 2: Show the deposit instructions

You get back `202 Accepted`, with the payment and where your customer must pay:

```json theme={null}
{
  "status": "success",
  "message": "Onramp payment initiated",
  "payment": {
    "id": "pay_8fK2mQ9xLp3VnR7tY1wZ4cB6dE",
    "status": "pending",
    "state": "initiated",
    "event_id": "evt_2cB6dE8fK2mQ9xLp3VnR7tY1wZ",
    "deposit": {
      "type": "bank_transfer",
      "currency": "NGN",
      "details": {
        "account_name": "Bridge / Ada Obi",
        "account_number": "9912345678",
        "bank_name": "Example Bank"
      },
      "amount": "150000",
      "amount_expected": "150150",
      "processor_fee": "150"
    }
  }
}
```

| Field | What it means |
| - | - |
| `deposit.details` | Where to pay. For a bank transfer: `account_name`, `account_number`, `bank_name`. |
| `deposit.amount_expected` | **The exact amount to send.** It may include a processing fee on top of the quote amount. |
| `deposit.processor_fee`, `deposit.vat` | Fees included in `amount_expected`, when there are any. |
| `note` | Sometimes included, with extra instructions for your customer. |

> [!WARNING]
>
> Ask your customer to send **exactly** `amount_expected`, in one transfer, within 30 minutes. If nothing arrives in that time, the payment fails. Paying a different amount puts the payment on hold for a review (`flagged`).

## Step 3: Wait for webhooks

As the payment moves, we send you [webhooks](https://docs.bridge.linkio.world/webhooks/onramp-events.md). You can also check at any time with [Get a payment](https://docs.bridge.linkio.world/api-reference/payments/details.md).

## Every state

| State | Status | What it means | What to do |
| - | - | - | - |
| `initiated` | `pending` | We've created the payment. | Show the deposit instructions. |
| `quote_locked` | `pending` | The price is held for this payment. | Nothing. |
| `fiat_pending` | `pending` | We're waiting for your customer's money. | Remind your customer to pay. |
| `fiat_received` | `pending` | Your customer's money has arrived. | Tell your customer it's on its way. |
| `processing` | `pending` | We're sending the stablecoin to the wallet. | Nothing. |
| `completed` | `success` | The stablecoin has been sent. `transaction_hash` shows the blockchain transaction. | Show success. |
| `failed` | `failed` | The payment couldn't be finished, for example no money arrived within 30 minutes. | Tell your customer. Start again with a new quote if they want to retry. |
| `canceled` | `failed` | The payment was canceled, for example the customer declined it. | Tell your customer. |
| `reversed` | `failed` | The payment was undone after going through. | Contact support for details. |
| `flagged` | `pending` | The payment is on hold for a review, for example because the amount paid didn't match. | Wait. After the review, the payment moves to `completed` or `failed`. |
| `paused` | `pending` | The payment is on hold. | Wait for `resumed`. |

`completed`, `failed`, `canceled`, and `reversed` are final. A payment in one of these states won't change again.

```mermaid theme={null}
flowchart LR
  initiated --> quote_locked --> fiat_pending --> fiat_received --> processing --> completed
  fiat_pending --> failed
  fiat_pending --> canceled
  fiat_pending --> flagged
  processing --> flagged
  flagged --> completed
  flagged --> failed
  processing --> failed
  completed --> reversed
```

## What can go wrong when you create it

| Status | Code | Why | What to do |
| - | - | - | - |
| `422` | `validation_failed` | A field is wrong. The `errors` list says which. Common ones are below. | Fix and retry. |
| `400` | `quote_expired` | The quote has expired. | [Refresh it](https://docs.bridge.linkio.world/api-reference/quotes/refresh.md) and retry. |
| `409` | `quote_locked` | The quote was already used for another payment. | Get a new quote. |
| `404` | `quote_not_found` | The quote doesn't exist. | Get a new quote. |
| `404` | `customer_not_found` | The customer doesn't exist. | Check the ID. |
| `409` | `customer_banned` | The customer is banned. | Contact support. |
| `403` | `pair_not_enabled` | The pair is turned off for your business. | See [Currency pairs](https://docs.bridge.linkio.world/control-app/ramp-pairs.md). |
| `409` | `idempotency_conflict` | Same `Idempotency-Key` still running, or used with a different body. | See [Retries](https://docs.bridge.linkio.world/before-you-build/safe-requests.md). |
| `422` | `payment_processing_failed` | Payments couldn't be created. | Read the message. Try again with a new quote. |
| `503` | `route_temporarily_unavailable` | The route is temporarily down. | Try again later. |
| `504` | `request_timeout` | The request took too long to process | Retry with the **same** `Idempotency-Key`. |

Common `validation_failed` messages:

| Field | Message |
| - | - |
| `payment_details` | `payment_details (the receiving wallet) is required` |
| `payment_details.type` | `Onramp payment_details type must be stablecoin` |
| `payment_details.currency` | `Wallet currency must match the quote target_currency` |
| `payment_details.details.network` | `Wallet network must match the quote network` |
| `payment_details.details.address` | The address isn't valid for the network. |
| `customer_id` | `customer_id does not match the customer on this quote` |
| `customer_id` | `Customer first_name and last_name are required to initiate an onramp` |
| `quote_id` | `Quote qte_… cannot be used for onramp payments` (it's an offramp quote) |
| `payer` | `payer (the mobile money wallet that pays) is required for momo onramps` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.