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

# Send USDT withdrawals and offramp funds

> Withdraw USDT to an external crypto wallet or convert funds to NGN via an offramp. Includes bank payout setup and status tracking.

Dubu Pay gives you two ways to move money out: a **withdrawal** sends USDT directly to an on-chain wallet address, and an **offramp** converts USDT back to NGN and pays out to a Nigerian bank account. This guide covers both flows, including how to create recipients you can reuse across multiple transfers.

## USDT withdrawals

A withdrawal moves USDT from your merchant balance to any external wallet address on a supported chain.

### Initiate a withdrawal

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dubupay.com/api/v1/payments/withdrawals \
    -H "X-Api-Key: dubu_sk_live_your_api_key" \
    -H "Content-Type: application/json" \
    -H "X-Idempotency-Key: unique-key-for-this-request" \
    -d '{
      "amount_usd": "50.00",
      "token": "USDT",
      "chain": "TRON",
      "destination_address": "TYour_TRON_wallet_address_here"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.dubupay.com/api/v1/payments/withdrawals",
    {
      method: "POST",
      headers: {
        "X-Api-Key": "dubu_sk_live_your_api_key",
        "Content-Type": "application/json",
        "X-Idempotency-Key": "unique-key-for-this-request",
      },
      body: JSON.stringify({
        amount_usd: "50.00",
        token: "USDT",
        chain: "TRON",
        destination_address: "TYour_TRON_wallet_address_here",
      }),
    }
  );
  const { data: withdrawal } = await response.json();
  ```
</CodeGroup>

**Request fields:**

| Field                 | Type   | Required | Description                                                                                    |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
| `amount_usd`          | string | Yes      | Amount in USD to withdraw (e.g. `"50.00"`)                                                     |
| `token`               | string | Yes      | Token to send (e.g. `"USDT"`)                                                                  |
| `chain`               | string | Yes      | Destination chain. Supported: `APTOS`, `BASE`, `CELO`, `ETHEREUM`, `POLYGON`, `SOLANA`, `TRON` |
| `destination_address` | string | Yes      | On-chain wallet address for the recipient                                                      |

<Tip>
  Always pass `X-Idempotency-Key` with a unique value per withdrawal. If the request times out and you retry, the same key prevents a duplicate withdrawal from being created.
</Tip>

**Response:**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "wdraw_01hx8ka2qr3st4uv5wx6ef",
    "status": "PENDING",
    "amount_usd": "50.00",
    "token": "USDT",
    "chain": "TRON",
    "destination_address": "TYour_TRON_wallet_address_here",
    "created_at": "2024-11-01T10:00:00.000Z"
  }
}
```

### Check withdrawal status

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.dubupay.com/api/v1/payments/withdrawals/wdraw_01hx8ka2qr3st4uv5wx6ef \
    -H "X-Api-Key: dubu_sk_live_your_api_key"
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.dubupay.com/api/v1/payments/withdrawals/wdraw_01hx8ka2qr3st4uv5wx6ef",
    {
      headers: { "X-Api-Key": "dubu_sk_live_your_api_key" },
    }
  );
  const { data: withdrawal } = await response.json();
  ```
</CodeGroup>

```json theme={null}
{
  "success": true,
  "data": {
    "id": "wdraw_01hx8ka2qr3st4uv5wx6ef",
    "status": "SETTLED",
    "amount_usd": "50.00",
    "token": "USDT",
    "chain": "TRON",
    "destination_address": "TYour_TRON_wallet_address_here",
    "tx_hash": "0xdef456...",
    "settled_at": "2024-11-01T10:03:11.000Z"
  }
}
```

**Withdrawal statuses:**

| Status      | Meaning                                 |
| ----------- | --------------------------------------- |
| `PENDING`   | Withdrawal created, awaiting processing |
| `SUBMITTED` | Submitted to the blockchain             |
| `SETTLED`   | Transaction confirmed on-chain          |
| `FAILED`    | Withdrawal could not be completed       |

## Offramps — convert USDT to NGN

An offramp converts USDT to NGN and pays out to a Nigerian bank account. You need to provide the recipient's bank details either inline or via a pre-created recipient.

### Initiate an offramp

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dubupay.com/api/v1/payments/offramps \
    -H "X-Api-Key: dubu_sk_live_your_api_key" \
    -H "Content-Type: application/json" \
    -H "X-Idempotency-Key: unique-key-for-this-offramp" \
    -d '{
      "type": "TEMPORARY",
      "customer": {
        "email": "amara@example.com",
        "first_name": "Amara",
        "last_name": "Okafor"
      },
      "chain": "TRON",
      "asset": "USDT",
      "settlement": {
        "mode": "NGN_PAYOUT",
        "rate_id": "rate_01hx8kb2qr3st4uv5wx6gh",
        "destination_bank": {
          "account_name": "Amara Okafor",
          "account_number": "0123456789",
          "bank_code": "044"
        }
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.dubupay.com/api/v1/payments/offramps",
    {
      method: "POST",
      headers: {
        "X-Api-Key": "dubu_sk_live_your_api_key",
        "Content-Type": "application/json",
        "X-Idempotency-Key": "unique-key-for-this-offramp",
      },
      body: JSON.stringify({
        type: "TEMPORARY",
        customer: {
          email: "amara@example.com",
          first_name: "Amara",
          last_name: "Okafor",
        },
        chain: "TRON",
        asset: "USDT",
        settlement: {
          mode: "NGN_PAYOUT",
          rate_id: "rate_01hx8kb2qr3st4uv5wx6gh",
          destination_bank: {
            account_name: "Amara Okafor",
            account_number: "0123456789",
            bank_code: "044",
          },
        },
      }),
    }
  );
  const { data: offramp } = await response.json();
  ```
</CodeGroup>

**Key request fields:**

| Field                         | Type   | Required          | Description                                                       |
| ----------------------------- | ------ | ----------------- | ----------------------------------------------------------------- |
| `type`                        | string | Yes               | `TEMPORARY` or `PERMANENT`                                        |
| `customer.customer_id`        | string | —                 | Existing customer ID (required if no `email`)                     |
| `customer.email`              | string | —                 | Customer email (required if no `customer_id`)                     |
| `chain`                       | string | Yes               | Chain from which USDT will be sent                                |
| `asset`                       | string | Yes               | `USDT` or `USDC`                                                  |
| `settlement.mode`             | string | Yes               | `NGN_PAYOUT` to bank, or `INTERNAL_BALANCE` to credit your ledger |
| `settlement.rate_id`          | string | Yes (NGN\_PAYOUT) | Rate ID obtained from `GET /payments/rates`                       |
| `settlement.destination_bank` | object | Yes (NGN\_PAYOUT) | Bank account details for the NGN payout                           |

<Note>
  Fetch the current exchange rate before creating an offramp: `GET /payments/rates`. The `rate_id` from the response is time-limited, so use it promptly.
</Note>

**Response:**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "offramp_01hx8kc2qr3st4uv5wx6ij",
    "type": "TEMPORARY",
    "status": "ACTIVE",
    "chain": "TRON",
    "asset": "USDT",
    "deposit_address": "TReceiving_address_for_USDT",
    "settlement": {
      "mode": "NGN_PAYOUT",
      "destination_bank": {
        "account_name": "Amara Okafor",
        "account_number": "0123456789",
        "bank_code": "044"
      }
    },
    "created_at": "2024-11-01T10:10:00.000Z"
  }
}
```

The `deposit_address` is the on-chain address where USDT should be sent to trigger the NGN payout.

### Check offramp status

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.dubupay.com/api/v1/payments/offramps/offramp_01hx8kc2qr3st4uv5wx6ij \
    -H "X-Api-Key: dubu_sk_live_your_api_key"
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.dubupay.com/api/v1/payments/offramps/offramp_01hx8kc2qr3st4uv5wx6ij",
    {
      headers: { "X-Api-Key": "dubu_sk_live_your_api_key" },
    }
  );
  const { data: offramp } = await response.json();
  ```
</CodeGroup>

**Offramp statuses:** `ACTIVE`, `INACTIVE`, `EXPIRED`

### List all offramps

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.dubupay.com/api/v1/payments/offramps?status=ACTIVE&asset=USDT" \
    -H "X-Api-Key: dubu_sk_live_your_api_key"
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({ status: "ACTIVE", asset: "USDT" });
  const response = await fetch(
    `https://api.dubupay.com/api/v1/payments/offramps?${params}`,
    {
      headers: { "X-Api-Key": "dubu_sk_live_your_api_key" },
    }
  );
  const { data } = await response.json();
  ```
</CodeGroup>

You can filter by `type`, `chain`, `asset`, `status`, and `settlement_mode`.

## Fetch the exchange rate

Before creating an offramp with `NGN_PAYOUT`, get the current rate and lock it:

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.dubupay.com/api/v1/payments/rates" \
    -H "X-Api-Key: dubu_sk_live_your_api_key"
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.dubupay.com/api/v1/payments/rates",
    {
      headers: { "X-Api-Key": "dubu_sk_live_your_api_key" },
    }
  );
  const { data: rates } = await response.json();
  // Use rates[0].id as rate_id in your offramp request
  ```
</CodeGroup>

<Warning>
  Rate IDs expire. Use the `rate_id` in your offramp request within the validity window returned by the rates endpoint. Creating an offramp with an expired rate ID will fail.
</Warning>
