FX
Query rates and execute FX trades.
Flutterwave’s FX service allows you to convert currencies directly for your desired use case. This provides easy access to liquidity for your payment operations.
Feature availability
The FX service is not available on your account by default. To request for this feature on your account, send an email to our support team.
The Process
The essential steps in using the FX service include:
- Request for a quote(RFQ) by sending a
POSTrequest to the create a request for quote endpoint . In your request, specify the trade amount, unique reference, and currency pair (e.g., NGN to USD). - We’ll send the FX rate, approved base quantity(amount), converted total value, and quote expiry to your Webhook server. You can also retrieve information on your quote by querying the check quote status endpoint using the
quote ID. - Initiate the trade by submitting the
quote IDto the create trade endpoint. - Monitor your trade: You can track executed trades by polling the check trade status endpoint or by reviewing your webhooks responses. Webhooks provide real-time updates on your trades, and polling should only be used as a backup.
Trading and live quotes are only available on weekdays (Mondays-Fridays).
Generating your Request for Quote (RFQ).
A quote contains the exchange information between currencies in an exchange pair. Before a trade can be made, a quote is requested and locked in. Quotes used for a trade can not be reused in subsequent trades.
To generate a request for quote or RFQ, send a request to the rfq endpoint with the following parameters:
base_currency: The currency you’re starting with (Possible Values: 'NGN', 'USD', 'GHS').target_currency: The currency you want to receive (Possible Values: 'NGN' and 'USD').quantity: The amount you want to transact. For example, when the base_currency is NGN and the target_currency is USD, specifying a quantity of 2000000 means you are set to use 2,000,000 NGN to purchase USD. The minimum trade amount supported is 1000 USD or its equivalent in the base currency.reference: A unique identifier for this FX transaction (e.g., "20Qclap0o0800oo9ooo"). Use it to track and manage the quote. If this is not provided, it will be automatically generated.
curl --location 'https://api.flutterwave.com/v3/rfq' \
--header 'Authorization: Bearer {{YOUR_SECRET_KEY}}' \
--header 'Content-Type: application/json' \
--data '{
"base_currency": "NGN",
"target_currency": "USD",
"quantity": 15000000,
"reference": "20Qclap0o0800oo9ooo"
}'
{
"status": "success",
"message": "Request for quote submitted.",
"data": {
"id": "c085aab5-5938-46a7-90a9-22420c2a8d6e",
"reference": "c9f6539747ef11f1",
"quantity": 1617759,
"instrument": "NGN/USD",
"quote": null,
"status": "NEW",
"created_at": "2026-02-06T10:03:22.000Z"
}
}
{
"status": "error",
"message": "A quote with this reference already exists.",
"data": null
}
{
"status": "error",
"message": "target_currency is required",
"data": null
}
Supported Currency Pairing:
base_currency target_currency NGN USD GHS USD USD NGN
/
Check Quote Status
When a quote is first generated, it has a status of NEW. To check the updated status of a requested quote, make a GET request to the check quote status endpoint using the quote id (data.id) returned from the RFQ request.
curl --location 'https://api.flutterwave.com/v3/rfq/570e21b4-7cf2-4048-bd1b-284bd45762c6' \
--header 'Authorization: Bearer {{secret_key}}'
A quote can have any of these statuses:
READY– The quote is active and can be used to execute a trade.PROCESSING– The quote has been submitted for a trade and is being processed.EXPIRED– The quote has expired and can no longer be used.FAILED– The request to create a quote failed. This mostly occurs due to an unsupported currency pairing or because a minimum trade amount of 1000 USD or its equivalent in the base currency was not provided.
If the quote's status is ready, the response includes a quote object with the following details:
best_available_rate: The real-time exchange rate applied to the trade. This rate is used to convert theapproved_quantityinto thetotal_value.approved_quantity: The actual amount in thebase_currency(e.g., NGN) approved for the trade. Kindly note that this may differ from your requestedquantitybased on limits or availability.total_value: The final converted amount you will receive in thetarget currency(e.g., USD). This is calculated asapproved_quantity×best_available_rate.expiry: The specific date and time the quote becomes invalid.
{
"status": "success",
"message": "Quote fetched successfully.",
"data": {
"id": "c085aab5-5938-46a7-90a9-22420c2a8d6e",
"reference": "c9f6539747ef11f1",
"quantity": 1617759,
"instrument": "NGN/USD",
"quote": {
"best_available_rate": "0.000659",
"approved_quantity": 1517759,
"total_value": 1000.203181,
"expiry": "2026-02-06T10:12:02.990Z"
},
"status": "READY",
"created_at": "2026-02-06T10:03:22.000Z",
"complete_message": "Your RFQ is ready"
}
}
When the submitted quote has been processed, you’ll receive a webhook event with the status READY similar to the one below:
{
"event": "quote.completed",
"event.type": "QUOTE",
"data": {
"id": "c085aab5-5938-46a7-90a9-22420c2a8d6e",
"reference": "c9f6539747ef11f1",
"quantity": 1617759,
"instrument": "NGN/USD",
"quote": {
"best_available_rate": 0.000659,
"approved_quantity": 1517759,
"total_value": 1000.203181,
"expiry": "2026-02-06T10:12:02.990Z"
},
"status": "READY",
"created_at": "2026-02-06T10:03:22.000Z",
"complete_message": "Your RFQ is ready"
}
}
If you're satisfied with the quote, you can proceed to initiate the trade.
Quotes expire after 5 minutes. Always confirm that your quote hasn’t expired before initiating a trade to avoid request failure.
{
"status": "success",
"message": "Quote fetched successfully.",
"data": {
"id": "c085aab5-5938-46a7-90a9-22420c2a8d6e",
"reference": "c9f6539747ef11f1",
"quantity": 1617759,
"instrument": "NGN/USD",
"quote": {
"best_available_rate": "0.000659",
"approved_quantity": 1517759,
"total_value": 1000.203181,
"expiry": "2026-02-06T10:12:02.990Z"
},
"status": "EXPIRED",
"created_at": "2026-02-06T10:03:22.000Z",
"complete_message": "Your RFQ is expired"
}
}
Initiating a Trade
Using the quote, you can execute your trade. To execute a trade, send a request to the create trade endpoint with the following parameters:
quote_id: The unique ID of the quote you want to use (This is the data.id returned in the create quote response or webhook).narration: A short note for your own records, describing the purpose of the transaction.
curl --location 'https://api.flutterwave.com/v3/trade' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{secret_key}}' \
--data '{
"quote_id": "570e21b4-7cf2-4048-bd1b-284bd45762c6",
"narration": "sample exchange"
}'
A successfully initiated trade response contains details such as:
quantity: The initial amount passed when creating a quote request.
approved_quantity: The actual amount in the base_currency(e.g., NGN) approved for the trade.
price: The final converted amount you will receive in the target currency (e.g., USD).
{
"status": "success",
"message": "Trade initiated successfully.",
"data": {
"id": "30b85c03-9124-4418-b30b-0495ffbdc633",
"quantity": 1617759,
"approved_quantity": 1517759,
"price": 1000.203181,
"instrument": "NGN/USD",
"status": "NEW",
"recipient": {
"id": "200004751",
"name": "MythCo."
},
"narration": "sample exchange",
"reference": "TRD-86ade016977958e2",
"callback_url": "https://www.helloajadi.com/api/flw/dev",
"created_at": "2026-02-06T10:08:54.000Z"
}
}
{
"status": "error",
"message": "This quote does not exist.",
"data": null
}
Fetching Trade Status
All newly created trades have a status ofNEW. To fetch the updated status of the trade, make a GET request to the Check Trade Status endpoint using the data.id from the trade initiation response.
curl --location 'https://api.flutterwave.com/v3/trade/96086092-fca3-4f9b-9ce1-90633a9d300a' \
--header 'Authorization: Bearer {{secret_key}}'
The trade status can be:
-
PENDING– The trade is still in progress. -
FAILED– The trade failed to complete. -
SETTLED` – The trade was completed.
{
"status": "success",
"message": "Trade item fetched.",
"data": {
"id": "30b85c03-9124-4418-b30b-0495ffbdc633",
"quantity": 1617759,
"price": 1000.203181,
"instrument": "NGN/USD",
"status": "SETTLED",
"response_message": "Transfer was Successful",
"recipient": {
"id": "200004751",
"name": "MythCo."
},
"narration": "sample exchange",
"reference": "TRD-86ade016977958e2",
"callback_url": "https://www.helloajadi.com/api/flw/dev",
"created_at": "2026-02-06T10:08:54.000Z"
}
}
Handling Webhooks
Whenever the quote and trade status change, Flutterwave will send a webhook notification to your system. Below are examples of expected webhook structures.
Quote Events(quote.completed):
{
"event": "quote.completed",
"event.type": "QUOTE",
"data": {
"id": "342cd6e7-9015-48c7-92a6-7a81e70bb6f4",
"reference": "5bddd594b3025ce9",
"quantity": 100000,
"instrument": "GHS/USD",
"quote": null,
"status": "PROCESSING",
"created_at": "2026-02-03T11:35:55.000Z",
"complete_message": "Your RFQ is being processed"
}
}
{
"event": "quote.completed",
"event.type": "QUOTE",
"data": {
"id": "c085aab5-5938-46a7-90a9-22420c2a8d6e",
"reference": "c9f6539747ef11f1",
"quantity": 1617759,
"instrument": "NGN/USD",
"quote": {
"best_available_rate": 0.000659,
"approved_quantity": 1517759,
"total_value": 1000.203181,
"expiry": "2026-02-06T10:12:02.990Z"
},
"status": "READY",
"created_at": "2026-02-06T10:03:22.000Z",
"complete_message": "Your RFQ is ready"
}
}
{
"event": "quote.completed",
"event.type": "QUOTE",
"data": {
"id": "0498f583-b536-4d70-a842-634e155c58b8",
"reference": "eeca16e015cf423b",
"quantity": 15000,
"instrument": "NGN/USD",
"quote": null,
"status": "FAILED",
"created_at": "2026-02-03T14:40:34.000Z",
"complete_message": "Minimum amount is NGN 1517759.30. Please increase the amount or use the regular flow."
}
}
{
"event": "quote.completed",
"event.type": "QUOTE",
"data": {
"id": "c085aab5-5938-46a7-90a9-22420c2a8d6e",
"reference": "c9f6539747ef11f1",
"quantity": 1617759,
"instrument": "NGN/USD",
"quote": {
"best_available_rate": "0.000659",
"approved_quantity": 1517759,
"total_value": 1000.203181,
"expiry": "2026-02-06T10:12:02.990Z"
},
"status": "EXPIRED",
"created_at": "2026-02-06T10:03:22.000Z",
"complete_message": "Your RFQ is expired"
}
}
Trade Events(transfer.completed):
{
"event": "transfer.completed",
"event.type": "Transfer",
"data": {
"id": 255169,
"account_number": "42769239",
"bank_name": "wallet",
"bank_code": "wallet",
"fullname": "uzoma kenkwo",
"created_at": "2026-02-03T12:03:59.000Z",
"currency": "USD",
"debit_currency": "NGN",
"amount": 629.705485,
"fee": 0,
"status": "SUCCESSFUL",
"reference": "TRD-7fbcf39210578e4d",
"meta": {
"quote_id": "526366c8-93e7-7dcc-0dd8-00006d4d54f1",
"AccountId": 1668495,
"merchant_id": "300408963"
},
"narration": "sample exchange",
"approver": null,
"complete_message": "Transfer was successful",
"requires_approval": 0,
"is_approved": 1
}
}
{
"event": "transfer.completed",
"event.type": "Transfer",
"data": {
"id": 255169,
"account_number": "42769239",
"bank_name": "wallet",
"bank_code": "wallet",
"fullname": "uzoma kenkwo",
"created_at": "2026-02-03T12:03:59.000Z",
"currency": "USD",
"debit_currency": "NGN",
"amount": 629.705485,
"fee": 0,
"status": "FAILED",
"reference": "TRD-7fbcf39210578e4d",
"meta": {
"quote_id": "342cd6e7-9015-48c7-92a6-7a81e70bb6f4",
"AccountId": 1668495,
"merchant_id": "300408963"
},
"narration": "sample exchange",
"approver": null,
"complete_message": "DISBURSE FAILED: Insufficient balance in customer's wallet",
"requires_approval": 0,
"is_approved": 1
}
}
Once a trade is completed successfully, the funds will be settled in the target currency's payout wallet.
Updated about 16 hours ago
