Auto Swap guide

Auto Swap lets a customer buy crypto by transferring money to a reusable bank account. You set up the account once and link it to a crypto wallet. Each eligible deposit automatically starts a new conversion and sends the crypto to that wallet.

For example, a customer can deposit NGN into their assigned account today and deposit again next week. Both deposits use the same bank account and create separate buy transactions.

Before you start

You need:

  • A merchant app approved for Auto Swap.
  • Your app's private API key and an allowed server IP if IP restrictions are enabled.
  • The customer's name, email, and BVN for an NGN account.
  • A supported crypto currency, network, and valid destination wallet address.
  • A webhook URL configured for your app so you can receive transaction updates.

This guide uses NGN deposits as an example. Confirm available currencies, networks, banks, and transaction limits for your app before accepting payments.

Base URL: https://ramp-be.quidax.io/

All sample endpoint URLs below include this base URL.

Send requests from your server with these headers:

X-Private-Key: YOUR_PRIVATE_KEY
Content-Type: application/json
Accept: application/json

1. Create an Auto Swap

Documentation reference: Create an Auto Swap.

POST https://ramp-be.quidax.io/api/v1/merchants/custodial/auto_swap/initiate_onramp

Send the customer's details, the crypto destination, and the bank account setup details:

{
  "merchant_reference": "customer-1042-auto-swap",
  "customer": {
    "email": "[email protected]",
    "first_name": "Ada",
    "last_name": "Okafor"
  },
  "coin_deposit": {
    "address": "0x1111111111111111111111111111111111111111",
    "network": "erc20",
    "currency": "usdt"
  },
  "bank_account": {
    "bvn": "11111111111",
    "bank_code": "000",
    "currency": "ngn"
  }
}

Replace all placeholder values with valid details. Use lowercase currency and network codes. Send the BVN and bank code as strings so leading zeros are preserved. For NGN, the BVN must contain exactly 11 digits. You can retrieve bank codes from GET https://ramp-be.quidax.io/api/v1/merchants/custodial/banks?country=NG.

coin_deposit specifies the wallet that will receive the purchased crypto. The wallet address must match the network. This endpoint does not accept a destination tag or memo.

merchant_reference is optional, but supplying a unique value for each Auto Swap makes it easier to retrieve the setup and its transactions later. It identifies the reusable setup, rather than an individual deposit.

A successful response returns HTTP 200:

{
  "status": "ok",
  "message": "Auto swap successfully created",
  "data": {
    "public_id": "550e8400-e29b-41d4-a716-446655440000",
    "merchant_reference": "customer-1042-auto-swap",
    "customer": {
      "email": "[email protected]",
      "first_name": "Ada",
      "last_name": "Okafor"
    },
    "side": "onramp",
    "deposit_bank_account": {
      "account_number": "0123456789",
      "account_name": "Ada Okafor",
      "currency": "ngn",
      "bank_name": "Example Bank",
      "bank_code": "000"
    }
  }
}

The response values above are illustrative. Save the returned public_id, merchant_reference, and bank account details. Creating the Auto Swap prepares the account; a buy transaction starts when a deposit arrives.

2. Ask the customer to transfer money

Show the customer the returned bank name, account number, account name, and currency. Ask them to transfer from a bank account in their own name and stay within the applicable deposit limits.

After an eligible deposit is received:

  1. A separate buy transaction is created for that deposit.
  2. The deposit is checked, including applicable limits and payment checks.
  3. The conversion is processed and crypto is sent to the configured wallet.
  4. You receive webhook updates as the transaction progresses.

The customer can reuse the account for later deposits. You do not need to initiate or confirm a buy transaction for each payment. The crypto amount depends on the conversion rate and applicable fees when that deposit is processed.

3. Track each transaction

Auto Swap transactions use the standard buy transaction webhooks:

EventWhat it means
buy_transaction.processingThe buy transaction is processing.
buy_transaction.successfulThe transaction has completed.
buy_transaction.failedThe transaction failed or needs attention. Check data.status and data.error_message.

Transaction payloads include data.public_id, data.merchant_reference, data.status, and an auto_swap object. Use data.auto_swap.public_id or data.auto_swap.merchant_reference to connect the transaction to the original setup. The transaction's own merchant_reference is generated separately for each deposit.

Treat a transaction as completed when its status is completed. A processing event or a successful setup response does not confirm that crypto has been delivered.

Verify each webhook's X-Ramp-Signature using HMAC-SHA256 over the exact raw request body with your active app secret key. Return HTTP 2xx promptly after accepting it. Deliveries may repeat, so handle duplicates safely using the event name and data.public_id.

Transaction webhook examples

Webhooks arrive as HTTP POST requests with an event name and a data object. All values below are illustrative. Each example shows the complete payload for the illustrated transaction state, including every field in auto_swap, its customer, and its deposit_bank_account.

Processing — buy_transaction.processing

This example shows a deposit that has been accepted while the conversion and payout are still in progress. crypto_payout can be null until a payout has been created.

{
  "event": "buy_transaction.processing",
  "data": {
    "public_id": "761a489c-79e3-48ab-b94e-29b5a69ba950",
    "status": "processing",
    "mode": "buy",
    "from_currency": "ngn",
    "to_currency": "usdt",
    "from_amount": "100000.0",
    "to_amount": "61.75",
    "network": "erc20",
    "merchant_reference": "AUTO_SWAP_2b626e77-2185-4b9a-85b7-d762758956a3",
    "error_message": null,
    "blockchain_fee": "0.75",
    "crypto_payout": null,
    "fiat_deposit": {
      "public_id": "59e4b578-a26b-44b9-9753-8e09f086c5f1",
      "status": "success",
      "currency": "ngn",
      "amount": "100000.0",
      "fee": "100.0",
      "created_at": "2026-10-18T09:00:00.000Z",
      "updated_at": "2026-10-18T09:00:00.000Z"
    },
    "mobile_money_deposit": null,
    "auto_swap": {
      "public_id": "550e8400-e29b-41d4-a716-446655440000",
      "merchant_reference": "customer-1042-auto-swap",
      "customer": {
        "email": "[email protected]",
        "first_name": "Ada",
        "last_name": "Okafor"
      },
      "side": "onramp",
      "deposit_bank_account": {
        "account_number": "0123456789",
        "account_name": "Ada Okafor",
        "currency": "ngn",
        "bank_name": "Example Bank",
        "bank_code": "000"
      }
    },
    "created_at": "2026-10-18T09:00:00.000Z",
    "updated_at": "2026-10-18T09:00:00.000Z"
  }
}

Completed — buy_transaction.successful

The transaction and crypto payout are now completed. The payout includes its blockchain transaction hash. mobile_money_deposit is null for this bank transfer.

{
  "event": "buy_transaction.successful",
  "data": {
    "public_id": "761a489c-79e3-48ab-b94e-29b5a69ba950",
    "status": "completed",
    "mode": "buy",
    "from_currency": "ngn",
    "to_currency": "usdt",
    "from_amount": "100000.0",
    "to_amount": "61.75",
    "network": "erc20",
    "merchant_reference": "AUTO_SWAP_2b626e77-2185-4b9a-85b7-d762758956a3",
    "error_message": null,
    "blockchain_fee": "0.75",
    "crypto_payout": {
      "public_id": "da715b49-203b-40b7-8d7b-2ddaa9a849db",
      "transaction_hash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "status": "completed",
      "amount": "61.75",
      "processor_fee": "0.75",
      "address": "0x1111111111111111111111111111111111111111",
      "network": "erc20",
      "currency": "usdt",
      "created_at": "2026-10-18T09:00:15.000Z",
      "updated_at": "2026-10-18T09:02:00.000Z"
    },
    "fiat_deposit": {
      "public_id": "59e4b578-a26b-44b9-9753-8e09f086c5f1",
      "status": "success",
      "currency": "ngn",
      "amount": "100000.0",
      "fee": "100.0",
      "created_at": "2026-10-18T09:00:00.000Z",
      "updated_at": "2026-10-18T09:00:00.000Z"
    },
    "mobile_money_deposit": null,
    "auto_swap": {
      "public_id": "550e8400-e29b-41d4-a716-446655440000",
      "merchant_reference": "customer-1042-auto-swap",
      "customer": {
        "email": "[email protected]",
        "first_name": "Ada",
        "last_name": "Okafor"
      },
      "side": "onramp",
      "deposit_bank_account": {
        "account_number": "0123456789",
        "account_name": "Ada Okafor",
        "currency": "ngn",
        "bank_name": "Example Bank",
        "bank_code": "000"
      }
    },
    "created_at": "2026-10-18T09:00:00.000Z",
    "updated_at": "2026-10-18T09:02:00.000Z"
  }
}

Failed or needs attention — buy_transaction.failed

This separate deposit requires review because the depositor's name did not match the customer. The event name is buy_transaction.failed, while data.status is needs_attention. Check both the status and error message before updating your customer.

{
  "event": "buy_transaction.failed",
  "data": {
    "public_id": "f754d486-5c17-4487-ae19-dfe0cd2d538c",
    "status": "needs_attention",
    "mode": "buy",
    "from_currency": "ngn",
    "to_currency": "usdt",
    "from_amount": "100000.0",
    "to_amount": "61.75",
    "network": "erc20",
    "merchant_reference": "AUTO_SWAP_9de51b2d-13f4-4356-a6f4-14c6a04ab368",
    "error_message": "Depositor name doesn’t match the account name.",
    "blockchain_fee": "0.75",
    "crypto_payout": null,
    "fiat_deposit": {
      "public_id": "a13e7cc6-e7b1-48eb-973c-e399aaf663cd",
      "status": "submitted",
      "currency": "ngn",
      "amount": "100000.0",
      "fee": "100.0",
      "created_at": "2026-10-18T11:00:00.000Z",
      "updated_at": "2026-10-18T11:00:00.000Z"
    },
    "mobile_money_deposit": null,
    "auto_swap": {
      "public_id": "550e8400-e29b-41d4-a716-446655440000",
      "merchant_reference": "customer-1042-auto-swap",
      "customer": {
        "email": "[email protected]",
        "first_name": "Ada",
        "last_name": "Okafor"
      },
      "side": "onramp",
      "deposit_bank_account": {
        "account_number": "0123456789",
        "account_name": "Ada Okafor",
        "currency": "ngn",
        "bank_name": "Example Bank",
        "bank_code": "000"
      }
    },
    "created_at": "2026-10-18T11:00:00.000Z",
    "updated_at": "2026-10-18T11:00:01.000Z"
  }
}

Retrieve transaction history

You can also retrieve the setup and its transaction history:

PurposeEndpointDocumentation reference
List your Auto SwapsGET https://ramp-be.quidax.io/api/v1/merchants/custodial/auto_swapsList Auto Swaps
Retrieve one setupGET https://ramp-be.quidax.io/api/v1/merchants/custodial/auto_swap/{merchant_reference}Retrieve an Auto Swap
List its buy transactionsGET https://ramp-be.quidax.io/api/v1/merchants/custodial/auto_swap/{merchant_reference}/transactionsList an Auto Swap's transactions

Use the setup's merchant reference for these paths and URL-encode it. For example:

GET https://ramp-be.quidax.io/api/v1/merchants/custodial/auto_swap/customer-1042-auto-swap/transactions?page=1&per_page=25

List responses contain a data array and meta pagination fields: page, per_page, total_count, total_pages, and next_page. Results are newest first. Pagination defaults to page 1 with 25 items; per_page is capped at 50.

The Auto Swap list supports a currency filter. The transaction list supports status, from_currency, and to_currency. Both support start_date and end_date; supply both together in YYYY-MM-DD format to filter by creation date.

4. Handle rejected deposits

Some deposits cannot proceed, such as payments below the minimum, above the allowed limit, or payments for which a conversion cannot be started. These can be recorded as rejected deposits and reported through auto_swap.deposit.rejected.

The event's data includes the rejected deposit's public_id, status, reason, amount, currency, deposit_reference, auto_swap_public_id, customer_email, and account_number, plus timestamps.

Rejected deposit webhook example

This illustrative deposit was below the applicable minimum. Its public_id identifies the rejected deposit, and auto_swap_public_id identifies the reusable setup.

{
  "event": "auto_swap.deposit.rejected",
  "data": {
    "public_id": "a54e27aa-66eb-4c69-b7c3-736cfce856cf",
    "status": "pending",
    "reason": "Deposit amount is below the minimum allowed",
    "amount": "500.0",
    "currency": "ngn",
    "deposit_reference": "KPY-DEP-1042",
    "auto_swap_public_id": "550e8400-e29b-41d4-a716-446655440000",
    "customer_email": "[email protected]",
    "account_number": "0123456789",
    "created_at": "2026-10-18T10:00:00.000Z",
    "updated_at": "2026-10-18T10:00:00.000Z"
  }
}

You can check rejected deposits through:

PurposeEndpointDocumentation reference
List rejected depositsGET https://ramp-be.quidax.io/api/v1/merchants/custodial/auto_swap/rejected_depositsList rejected Auto Swap deposits
Retrieve one rejected depositGET https://ramp-be.quidax.io/api/v1/merchants/custodial/auto_swap/rejected_deposits/{public_id}Retrieve a rejected Auto Swap deposit

The list supports status, currency, date filters, and pagination. Rejected deposits are tracked separately from the setup's buy transaction history.

Rejected deposit statusMeaning
pendingThe deposit is awaiting resolution.
refundedThe refund has been marked as completed.
ignoredThe deposit has been closed without being marked as refunded.

A rejection does not automatically refund the payment. Contact support with the rejected deposit's public_id and deposit_reference for resolution. When a refund is marked as completed, you receive auto_swap.deposit.refunded.

Refunded deposit webhook example

The refund event uses the same rejected deposit public_id and deposit_reference. Its status changes to refunded; the reason still describes the original rejection.

{
  "event": "auto_swap.deposit.refunded",
  "data": {
    "public_id": "a54e27aa-66eb-4c69-b7c3-736cfce856cf",
    "status": "refunded",
    "reason": "Deposit amount is below the minimum allowed",
    "amount": "500.0",
    "currency": "ngn",
    "deposit_reference": "KPY-DEP-1042",
    "auto_swap_public_id": "550e8400-e29b-41d4-a716-446655440000",
    "customer_email": "[email protected]",
    "account_number": "0123456789",
    "created_at": "2026-10-18T10:00:00.000Z",
    "updated_at": "2026-10-18T12:30:00.000Z"
  }
}

A transaction that already exists can also require review, for example because the depositor's name does not match the customer's name. Check transaction status and error details as well as the rejected deposit list.

Repeating a setup request

Repeating a request for the same customer, fiat currency, crypto destination, and direction returns the existing Auto Swap. Reuse the same merchant reference when retrying.

If the existing setup has no merchant reference, you can attach one by repeating the setup request with a unique reference. Once assigned, that reference cannot be replaced through this endpoint. Each app also has a configurable limit on Auto Swaps per customer; contact support if you need more.

Common errors

Errors include a message explaining the issue:

HTTP statusMessageWhat to do
403Private key is required / Invalid private keyCheck the X-Private-Key header and your app's active key.
403IP address is not allowedUse a server IP permitted for your app.
400Feature disabledAsk support to enable Auto Swap for your app.
400Invalid BVN / Invalid bank codeCheck the customer's BVN and supported bank code.
400Invalid currency / Invalid network / Invalid currency networkUse a supported currency and network combination.
400Invalid wallet addressCheck the address against the selected network.
400Merchant reference has already been usedUse a reference unique to this setup within your app.
400Auto swap already exists with a different merchant referenceRetrieve the existing setup using its original reference.
400Auto swap limit reachedReuse an existing setup or contact support about your limit.
404Auto swap not foundCheck the setup's merchant reference and the app key being used.
429Too many requests. Try again later.Wait for the duration in Retry-After before retrying.

Did this page help you?