All providers

Flutterwave

payment·🇳🇬🇬🇭🇰🇪🇺🇬🇹🇿🇷🇼🇿🇦🇿🇲🇨🇲🇨🇮🇸🇳🇬🇦🇸🇱·Sandbox available

Flutterwave is a pan-African payments platform offering card, mobile money, bank transfer, USSD and wallet payment collection plus payouts to bank accounts, mobile money wallets and Flutterwave wallets across Nigeria, Ghana, Kenya, Uganda, Tanzania, Rwanda, South Africa, Zambia, Cameroon, Côte d'Ivoire, Senegal, Gabon and Sierra Leone.

Use with AI agents

After installing the plugin or adding the MCP server, prompt your agent:

Integrate Flutterwave v4 to accept card and mobile money payments and pay out to local bank accounts across multiple African markets.
Install the plugin →

Capabilities

CapabilityTypeStatusiMethodExample
create_chargesynchronousAvailablePOST
create_chargebacksynchronousAvailablePOST
create_customersynchronousAvailablePOST
create_orchestrator_chargesynchronousAvailablePOST
create_orchestrator_ordersynchronousAvailablePOST
create_orchestrator_transfersynchronousAvailablePOST
create_ordersynchronousAvailablePOST
create_payment_methodsynchronousAvailablePOST
create_refundsynchronousAvailablePOST
create_transfersynchronousAvailablePOST
create_transfer_ratesynchronousAvailablePOST
create_transfer_recipientsynchronousAvailablePOST
create_transfer_sendersynchronousAvailablePOST
create_virtual_accountsynchronousAvailablePOST
delete_transfer_recipientsynchronousAvailableDELETE
delete_transfer_sendersynchronousAvailableDELETE
get_chargesynchronousAvailableGET
get_chargebacksynchronousAvailableGET
get_customersynchronousAvailableGET
get_ordersynchronousAvailableGET
get_payment_methodsynchronousAvailableGET
get_refundsynchronousAvailableGET
get_settlementsynchronousAvailableGET
get_transfersynchronousAvailableGET
get_transfer_ratesynchronousAvailableGET
get_transfer_recipientsynchronousAvailableGET
get_transfer_sendersynchronousAvailableGET
get_virtual_accountsynchronousAvailableGET
get_wallet_balancesynchronousAvailableGET
get_wallet_statementsynchronousAvailableGET
list_bank_branchessynchronousAvailableGET
list_bankssynchronousAvailableGET
list_chargebackssynchronousAvailableGET
list_chargessynchronousAvailableGET
list_customerssynchronousAvailableGET
list_feessynchronousAvailableGET
list_mobile_networkssynchronousAvailableGET
list_orderssynchronousAvailableGET
list_payment_methodssynchronousAvailableGET
list_refundssynchronousAvailableGET
list_settlementssynchronousAvailableGET
list_transfer_recipientssynchronousAvailableGET
list_transfer_senderssynchronousAvailableGET
list_transferssynchronousAvailableGET
list_virtual_accountssynchronousAvailableGET
list_wallet_balancessynchronousAvailableGET
lookup_bank_accountsynchronousAvailablePOST
lookup_wallet_accountsynchronousAvailablePOST
retry_transfersynchronousAvailablePOST
search_customerssynchronousAvailablePOST
update_chargesynchronousAvailablePUT
update_chargebacksynchronousAvailablePUT
update_customersynchronousAvailablePUT
update_ordersynchronousAvailablePUT
update_transfersynchronousAvailablePUT
update_virtual_accountsynchronousAvailablePUT
webhook_eventwebhookAvailablePOST

Gotchas

create_charge

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • X-Trace-Id is REQUIRED on this endpoint (12-255 chars). Omitting it will return a 400 validation error — generate a fresh UUID per request and forward it through any retries.
  • authorization is an OBJECT, not a string. Set authorization.type to one of otp | pin | external_3ds | avs and provide the matching sub-object: { type: "otp", otp: { code } } / { type: "pin", pin: { nonce, encrypted_pin } } / { type: "external_3ds", external_3ds: { ... } } / { type: "avs", avs: { address: { ... } } }.
  • authorization.type=pin requires encrypted_pin obtained from Flutterwave's field-level encryption flow plus a 12-char alphanumeric nonce. Never send a raw PIN — the request will be rejected.
  • reference must be unique per merchant and match ^[a-zA-Z0-9-]+$ (6-42 chars). Reusing a reference returns a 409 Conflict — generate a fresh idempotent reference per attempt.
  • Successful creation returns HTTP 201, not 200. Treat both as success when reading response.ok.
  • When the charge needs customer action, the response status is "pending" and data.next_action carries a discriminated object. Handle every next_action.type explicitly — redirect_url (open url), requires_otp / requires_pin (collect from user, call update_charge), payment_instruction (display note), requires_bank_transfer (show virtual account), qr_code (render base64 image), requires_capture (call create_charge with order_id), requires_requery (poll get_charge), requires_additional_fields (collect listed fields).
  • fees[] is an array containing vat / app / merchant / stamp_duty entries — not a single number. Sum them client-side if you need a grand total.
  • Pass X-Idempotency-Key on retry-prone networks; replays return the original response instead of creating a duplicate charge.
  • customer_id and payment_method_id must already exist. Create the customer and tokenise the payment method via their dedicated endpoints first.
  • disputed and refunded are documented fields but absent from the response when false — they only appear when there is an active dispute or refund. Do not test for their absence to infer state; poll get_charge or listen to webhook events instead.

create_chargeback

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Required body fields are charge_id, amount, expiry (hours), and type (local|international). expiry sets the deadline (due_datetime) by which the chargeback must be resolved.
  • Always send X-Idempotency-Key (12-255 chars) on POST so retries do not create duplicate chargeback records.
  • Initial status can only be set to pending or initiated at creation; later transitions (accepted/declined/won/lost/...) are made through update_chargeback.
  • Returns 201 with the created chargeback in data. The chargeback then progresses asynchronously — poll get_chargeback or rely on webhook events for the final outcome.

create_customer

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • name and phone are nested objects, not flat strings. Sending name as a string (e.g. 'name': 'Jane Doe') triggers a 400 validation error — use { first, last } at minimum.
  • name.first / name.middle / name.last must each be 2-50 chars and contain only letters, spaces, hyphens, apostrophes, commas, and periods. Single-character names ('A') and digits in names fail validation.
  • phone.country_code must be 1-3 digits without the leading '+' (e.g. '234' for Nigeria). phone.number must be 7-10 digits, local subscriber portion only — leading zeros and the country code go in country_code.
  • Always send an X-Idempotency-Key header (12-255 chars) on POST /customers so safe retries do not create duplicate customer records. Also send X-Trace-Id for support traceability.
  • Creating a customer with an email that already exists for the merchant returns HTTP 409 (error.type 'RESOURCE_CONFLICT', code '10409') — handle this case by fetching the existing customer instead of treating it as a hard error.
  • Validation error items use the key 'field_name' (not 'field') — match on error.validation_errors[].field_name when surfacing per-field errors to users.
  • Flat fields like first_name, last_name, phone_number are silently ignored — the API accepts them without error but does not store them. Only nested objects (name.first, name.last, phone.country_code, phone.number) are stored and returned in responses. Always use nested format.

create_orchestrator_charge

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • X-Trace-Id is REQUIRED on this endpoint (12-255 chars). Omitting it returns a 400 validation error — generate a fresh UUID per request.
  • payment_method is an OBJECT, not a string. Set payment_method.type to one of card | bank_account | mobile_money | opay | applepay | googlepay | ussd and provide the matching sub-object (e.g. { type: "card", card: { nonce, encrypted_card_number, ... } } or { type: "mobile_money", mobile_money: { network, country_code, phone_number } }).
  • authorization is an OBJECT, not a string. Set authorization.type to one of otp | pin | external_3ds | avs and provide the matching sub-object: { type: "otp", otp: { code } } / { type: "pin", pin: { nonce, encrypted_pin } } / { type: "external_3ds", external_3ds: { ... } } / { type: "avs", avs: { address: { ... } } }.
  • customer is an OBJECT (not customer_id). Email is required; name, phone, and address are optional structured sub-objects. Flutterwave creates or matches the customer behind the scenes — use this endpoint for one-shot checkouts that bypass /customers and /payment-methods provisioning.
  • reference must be unique per merchant, match ^[a-zA-Z0-9-]+$ and be 6-42 chars. Reusing returns 409 Conflict.
  • Successful creation returns HTTP 201 (not 200). The response uses created_datetime (not created_at), customer_id (not nested customer), and a typed next_action object that you must dispatch on next_action.type.
  • Card field encryption is mandatory — never POST raw PAN/CVV. Generate nonce + encrypted_* values via Flutterwave's field-level encryption SDK.
  • Choose payment_method.type based on currency/country: card / applepay / googlepay are pan-African; mobile_money targets XAF/XOF/KES/UGX/RWF/TZS/GHS; opay and ussd target NGN; bank_account is region-dependent. A mismatch returns 400.
  • Pass X-Idempotency-Key (12-255 chars) on retry-prone networks; replays return the original response instead of creating a duplicate charge.

create_orchestrator_order

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • An order is the preauth side of the preauth-and-capture flow: it authorizes the amount but does not capture funds. To capture, call create_charge with order_id set to the id returned here (e.g. ord_WRq7L4TM8p).
  • Body shape is identical to create_orchestrator_charge — same inline customer object and same discriminated payment_method object — but the resource created is an order, not a charge. Do not confuse the two endpoints.
  • payment_method is an OBJECT, not a string. Set payment_method.type to one of card | bank_account | mobile_money | opay | applepay | googlepay | ussd and provide the matching sub-object (e.g. { type: "card", card: { nonce, encrypted_card_number, ... } }).
  • customer is an OBJECT (not customer_id). Email is required; name, phone, and address are optional structured sub-objects. Flutterwave creates or matches the customer behind the scenes.
  • X-Trace-Id is REQUIRED on this endpoint (12-255 chars). Omitting it returns a 400 validation error — generate a fresh UUID per request.
  • reference must be unique per merchant, match ^[a-zA-Z0-9-]+$ and be 6-42 chars. Reusing returns 409 Conflict.
  • Successful creation returns HTTP 201 (not 200) with status="success". The response uses created_datetime (not created_at) and customer_id (not nested customer).
  • Card field encryption is mandatory — never POST raw PAN/CVV. Generate nonce + encrypted_* values via Flutterwave's field-level encryption SDK.
  • Choose payment_method.type based on currency/country: card / applepay / googlepay are pan-African; mobile_money targets XAF/XOF/KES/UGX/RWF/TZS/GHS; opay and ussd target NGN; bank_account is region-dependent. A mismatch returns 400.

create_orchestrator_transfer

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • POST /direct-transfers is the orchestrator variant: it accepts inline sender and recipient objects and creates those records on the fly, returning their ids in the response. Use it when you do not want to pre-call POST /transfers/senders and POST /transfers/recipients. For repeated payouts to the same beneficiary, switch to POST /transfers with stored sender_id/recipient_id.
  • payment_instruction.type is a discriminator that fixes the destination corridor (bank_ngn, bank_usd, bank_gbp, bank_eur, bank_egp, bank_etb, bank_ghs, bank_mwk, bank_zar). The required shape of recipient and sender changes per type — sending a bank_ngn recipient under bank_usd returns 400 with validation_errors.
  • Reference is optional but if supplied must be 6-42 alphanumeric+hyphen characters and unique. Always send X-Trace-Id (12-255 chars, required) and X-Idempotency-Key (12-255 chars, optional but strongly recommended) so retries are safe and the request is correlated in Flutterwave's logs.
  • amount.applies_to controls whether amount.value is denominated in destination_currency or source_currency — pick deliberately on cross-currency corridors, because Flutterwave will FX the other side using its quoted rate.
  • For bank_egp, transfer_purpose is required and national_identification (type/identifier/expiration_date) plus date_of_birth on the sender are mandatory. Skipping them returns 400.
  • Response status values are UPPERCASE (NEW, PENDING, FAILED, SUCCESSFUL, CANCELLED, INITIATED). Do not lowercase before comparison.

create_order

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • reference must be unique per merchant and 6-42 characters (alphanumeric and hyphens); reusing a reference returns 409 RESOURCE_CONFLICT.
  • amount uses the major unit of the currency (e.g. 500 NGN, not 50000 kobo) — different from many v3 endpoints that took minor units.
  • Send X-Idempotency-Key (12-255 chars) when retrying order creation to guarantee at-most-once semantics; the same key returns the original response.
  • customer_id and payment_method_id must already exist; create them first via /customers and /payment-methods.
  • Successful response is 201 Created with status='success'. The response data may include next_action (3DS redirect, wallet auth) and processor_response — inspect them before fulfilling.
  • Errors: 10400 REQUEST_NOT_VALID (validation), 10401 UNAUTHORIZED (token), 10403 FORBIDDEN (scope), 10409 RESOURCE_CONFLICT (duplicate reference).

create_payment_method

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The body is a discriminated union on `type`. For each type the per-type sub-object key matches the type name (card, mobile_money, ussd, applepay, googlepay, bank_account, opay) — there is NO generic `details` field.
  • Card fields are field-level encrypted: send nonce (12 alphanumeric chars) plus encrypted_card_number / encrypted_expiry_month / encrypted_expiry_year (and optional encrypted_cvv). Generate these using the Flutterwave-provided SDK — never POST raw PAN/CVV directly.
  • mobile_money requires three fields: network (e.g. 'MTN'), country_code (digits only, 1-3 chars, e.g. '234'), phone_number (digits only, 7-10 chars, without country code).
  • ussd.account_bank is the Nigerian bank code (digits only, 3+ chars, e.g. '050').
  • applepay.card_holder_name / googlepay.card_holder_name accept 2-50 chars of letters, spaces, commas, periods, apostrophes and hyphens only.
  • bank_account and opay sub-objects are documented as reserved for future expansion — pass {} when registering those types.
  • Send X-Idempotency-Key (12-255 chars) when retrying creation to avoid duplicate stored instruments.
  • Successful response is 201 Created with the same per-type key echoed back plus created_datetime. customer_id, client_ip, and device_fingerprint are documented in the API but absent from sandbox responses — they may only be populated in production or for specific payment method types. Do not assume their absence means the creation failed.

create_refund

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Refund processing windows vary by payment method: card 3-15 days, mobile money 3-5 days, bank account ~24 hours. The HTTP response only confirms acceptance; final settlement comes later. Use get_refund to poll or the refund.completed webhook.
  • reason must be one of: duplicate, fraudulent, requested_by_customer, expired_uncaptured_charge. Any other value returns 400 REQUEST_NOT_VALID.
  • X-Trace-Id is required (12-255 chars) on this endpoint. Always also send X-Idempotency-Key on POST so retries do not create duplicate refunds.
  • Returns 201 with the created refund in data; the data object uses amount_refunded (not amount) and created_datetime (not created_at). 409 RESOURCE_CONFLICT typically means the charge has already been fully refunded or is in an incompatible state.

create_transfer

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • POST /transfers requires recipient_id (and sender_id for cross-border) pre-created via POST /transfers/recipients and POST /transfers/senders. Use POST /direct-transfers (orchestrator) if you want to inline sender/recipient data without pre-creating them.
  • payment_instruction.amount.applies_to controls whether amount.value is denominated in destination_currency or source_currency — pick deliberately on cross-currency corridors because Flutterwave will FX the other side using its quoted rate.
  • Always send X-Trace-Id (12-255 chars, required) and X-Idempotency-Key (12-255 chars, optional but strongly recommended) so retries are safe and the request is correlated in Flutterwave's logs.
  • When action is "scheduled", disburse_option (date_time YYYY-MM-DD HH:MM:SS + timezone) becomes required. When action is "deferred", the transfer is created in NEW/PENDING but not disbursed until PUT /transfers/{id} with initiate=true is called.
  • Reference is optional but if supplied must be 6-42 alphanumeric+hyphen characters and unique across all transactions — reusing returns 409 RESOURCE_CONFLICT.
  • Response status values are UPPERCASE (NEW, PENDING, FAILED, SUCCESSFUL, CANCELLED, INITIATED). Do not lowercase before comparison.

create_transfer_rate

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The destination side carries the amount (the amount the recipient receives); source only carries the currency. The API computes the source debit amount and returns both source.amount and destination.amount. Sending an amount on source is rejected as REQUEST_NOT_VALID.
  • Successful creation returns HTTP 201, not 200. Treat both as success when reading response.ok.
  • Numeric fields (rate, source.amount, destination.amount) are returned as STRINGS — not numbers. Parse them with Number() / parseFloat() (or keep them as strings if you need decimal precision).
  • precision must be between 2 and 9 inclusive (default 6); values outside this range return 400 REQUEST_NOT_VALID. The default of 6 is appropriate for most XOF/XAF/NGN/KES corridors.
  • X-Trace-Id is required on this endpoint (12-255 chars). Pass X-Idempotency-Key (12-255 chars) on retry-prone networks; replays return the original quote instead of producing a new one.

create_transfer_recipient

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Request body is a discriminated union keyed by `type` — the value determines which sibling object is required. For example, type="bank_ngn" requires a `bank` object with account_number + code; type="mobile_money_etb" requires `name.first` + `name.last` + `mobile_money.network` + `mobile_money.msisdn`. There is no generic `details` field.
  • Use the currency-specific type variant (e.g. bank_ngn, bank_eur, mobile_money_etb) — not a plain "bank" or "mobile_money" string. The variant determines validation rules and supported corridors.
  • X-Trace-Id is REQUIRED (not optional) on this endpoint and must be 12-255 characters. Generate a UUID per request for correlation; omitting it returns a 400 REQUEST_NOT_VALID.
  • Successful creation returns HTTP 201, not 200. The response.data echoes the recipient with sibling keys (bank, mobile_money) populated; use response.data.id for subsequent get/delete and to target transfers.
  • Pass X-Idempotency-Key (12-255 chars) on retry-prone networks; replays return the original response instead of creating a duplicate recipient.
  • Recipient IDs returned are opaque strings. Store the id field — do not apply encodeURIComponent when constructing path URLs for get/delete.

create_transfer_sender

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The request body is a discriminated union keyed by `type`. bank_egp and mobile_money_egp require the full KYC bundle (national_identification, date_of_birth, email, phone, address). bank_gbp and bank_eur drop national_identification and date_of_birth but keep email + phone + address. generic_sender only mandates name. Sending a partial KYC payload for bank_egp / mobile_money_egp returns 400 REQUEST_NOT_VALID.
  • Successful creation returns HTTP 201, not 200. Treat both as success when reading response.ok.
  • Pass X-Idempotency-Key (12-255 chars) on retry-prone networks; replays return the original response instead of creating a duplicate sender, or 409 RESOURCE_CONFLICT if it collides with a different request.
  • Sender IDs returned are opaque strings (e.g. "sender_12345abc"). Store data.id — you will need it to issue transfers and to call get/delete_transfer_sender. Do not apply encodeURIComponent when constructing path URLs.

create_virtual_account

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Required body fields are: customer_id, amount, reference, currency, account_type. Missing any one returns 400 REQUEST_NOT_VALID with validation_errors.
  • `expiry` is an INTEGER number of SECONDS (60 to 31,536,000), not an ISO 8601 timestamp. The response returns `account_expiration_datetime` as ISO 8601 — do not confuse the two.
  • `account_type` is mandatory: use `static` for a reusable fixed account number, `dynamic` for one-time per transaction. For static accounts pass `amount: 0` to accept any amount.
  • `reference` must be unique across ALL your transactions and 6–42 alphanumeric characters. Reusing a reference returns 409 RESOURCE_CONFLICT (code 10409).
  • `bvn` (11 digits, must start with 1–9) is typically required for Nigerian NGN accounts. `customer_account_number` is required when currency is EGP or KES.
  • customer_id must reference an existing customer — create the customer first via POST /customers (cus_... ids).
  • Successful creation returns HTTP 201, not 200. Settlement of funds is reported via the charge.completed webhook — wire it up before going live.

delete_transfer_recipient

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Successful deletion returns HTTP 204 No Content with an empty body — do not call response.json() on success. Treat response.ok (or status === 204) as the only signal of success.
  • Deletion is permanent — a deleted recipient id cannot be reused or restored. Subsequent calls to get_transfer_recipient with the same id return 404.
  • X-Idempotency-Key is not documented for DELETE — use X-Trace-Id (optional) for correlation instead.
  • Recipient IDs are opaque strings. Pass them as-is into the path — do not apply encodeURIComponent.

delete_transfer_sender

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Successful deletion returns HTTP 204 No Content with an empty body. Do not call response.json() — check response.status === 204 (or response.ok with content-length 0) and treat it as success.
  • Deletion is permanent — a deleted sender id cannot be reused or restored. Subsequent calls to get_transfer_sender with the same id return 404 NOT_FOUND.
  • DELETE has no request body. The documented headers are only Authorization and the optional X-Trace-Id — X-Idempotency-Key is not listed for this endpoint.
  • Sender IDs are opaque strings. Pass them as-is into the path — do not apply encodeURIComponent.

get_charge

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Always re-fetch the charge with get_charge before fulfilling the order. The synchronous create_charge response may return status "pending" when the customer must complete 3DS/OTP — only "succeeded" guarantees funds were captured.
  • data uses created_datetime (not created_at). Nested objects include billing_details (with name/phone sub-objects), fees[] (an array, not a single value), settlement_id[] (array of strings) and processor_response: { type, code }.
  • data.next_action is a DISCRIMINATED object — the key matching next_action.type holds the payload (e.g. next_action.redirect_url.url, next_action.requires_bank_transfer.account_number, next_action.qr_code.image). Inspect next_action.type before reading sub-fields.
  • data.payment_method_details.type is one of card | bank_account | mobile_money | opay | applepay | googlepay | ussd | bank_transfer. The type-keyed sub-object (e.g. payment_method_details.card.last4) carries the method-specific snapshot.
  • Unknown charge ids return 404. The error envelope is { status: "failed", error: { type, code, message } } — read error.message, not message at the top level.

get_chargeback

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The id path parameter is the Flutterwave-issued chargeback id, not the underlying charge_id.
  • A 404 (NOT_FOUND, code 10404) indicates the chargeback does not exist or is not visible under the merchant credentials in use.
  • Use stage AND status together to know what action is required: e.g. stage=pre-arbitration + status=pending means the merchant still needs to upload proof before due_datetime.

get_customer

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Customer IDs returned by Flutterwave are opaque strings (e.g. cus_3XarBILKQS). Do not apply encodeURIComponent to these IDs when constructing the path — pass them in the URL as-is.
  • Always send an X-Trace-Id header (12-255 chars) so Flutterwave support can find the request in their logs if anything goes wrong.
  • An unknown customer ID returns HTTP 404 with error.type 'NOT_FOUND' and code '10404', not an empty 200. Handle 404 separately from network errors.
  • Validation error items use the key 'field_name' (not 'field') — match on error.validation_errors[].field_name when surfacing per-field errors to users.

get_order

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Order date field is created_datetime (ISO 8601 with optional sub-second precision), not created_at.
  • id is the Flutterwave-assigned order id (prefix ord_), not the merchant reference. To look up by reference, use list_orders and match client-side.
  • payment_method_details and processor_response shapes depend on the underlying payment method type; treat unknown sub-fields permissively.
  • Always verify status server-side via this endpoint before fulfilling — never trust client-side redirects alone.

get_payment_method

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The response data shape is discriminated by `type`: the per-type sub-object key matches the type name (card, mobile_money, ussd, applepay, googlepay, bank_account, opay). There is no generic `details` envelope.
  • Date field is created_datetime, not created_at. Response also surfaces device_fingerprint and client_ip captured at creation time.
  • Sensitive details are masked (e.g. card last4 only) — never expect to retrieve the raw PAN or CVV.
  • type may include 'bank_transfer' in addition to the enum accepted by create_payment_method.

get_refund

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The id path parameter is the Flutterwave-issued refund id, not the underlying charge_id.
  • Refund status often stays pending/new for hours or days depending on payment method (card 3-15 days, mobile money 3-5 days, bank account ~24 hours). Poll this endpoint or subscribe to the refund.completed webhook for the final state.
  • The data object exposes amount_refunded (not amount) and created_datetime (not created_at).

get_settlement

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The id path parameter must be the Flutterwave-issued settlement id, not your merchant reference.
  • The response embeds the underlying charges array; page and size query params paginate that list (10 to 50 per page) and the page_info appears under meta.
  • Settlement amounts are split into net_amount (paid out) and gross_amount (before fees); use the fees[] breakdown plus chargeback and refund totals to reconcile.

get_transfer

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Always verify the transfer status server-side by polling GET /transfers/{id} or by listening on the transfer webhook before considering the payout final. Webhook delivery alone should be re-checked with this endpoint.
  • Pass id directly in the path. Flutterwave IDs (e.g. trf_yuK89vb) are URL-safe ASCII — do not URL-encode them.
  • data.status is uppercase (SUCCESSFUL, PENDING, etc.). Compare case-insensitively or normalise before persisting to your own ledger.

get_transfer_rate

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • id is the rate quote id returned by create_transfer_rate (e.g. "rte_r0cInAnTE"). There is no listing endpoint for rates — persist the id when you create the quote, otherwise you cannot retrieve it.
  • Numeric fields (rate, source.amount, destination.amount) are returned as STRINGS — not numbers. Parse them with Number() / parseFloat() (or keep them as strings if you need decimal precision).
  • Rate quote IDs are opaque strings. Pass them as-is into the path — do not apply encodeURIComponent.
  • The response includes created_datetime but no explicit expiration field. Rate quotes are intended to be consumed shortly after creation — fetch a fresh quote if more than a few minutes have passed since creation.

get_transfer_recipient

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • id is the Flutterwave-assigned recipient id (returned by create_transfer_recipient), not a merchant reference. To look up by reference, page through list_transfer_recipients and match client-side.
  • Type-specific destination details live on sibling keys (bank, mobile_money, wallet, cash_pickup, crypto) on the response data — read the `type` field first to know which sibling is populated.
  • Recipient IDs are opaque strings. Pass them as-is into the path — do not apply encodeURIComponent.
  • A 404 response means the recipient does not exist or has been deleted. The endpoint does not distinguish between these cases.

get_transfer_sender

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • id is the Flutterwave-assigned sender id (returned by create_transfer_sender), not a merchant reference. To look up by something else, page through list_transfer_senders and match client-side.
  • Sender IDs are opaque strings. Pass them as-is into the path — do not apply encodeURIComponent.
  • A 404 NOT_FOUND response means the sender does not exist or has been deleted. The endpoint does not distinguish between these cases.
  • The response shape depends on the sender variant that was created (bank_egp / mobile_money_egp / bank_gbp / bank_eur / generic_sender). Fields like national_identification or date_of_birth are absent on variants that did not require them.

get_virtual_account

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • data.account_type is "static" or "dynamic". Dynamic accounts have a non-null account_expiration_datetime; static accounts are permanent and may have account_expiration_datetime=null.
  • data.status is only "active" or "inactive". Use update_virtual_account with action_type="update_status" and status="inactive" to deactivate; there is no "expired" status — dynamic accounts simply stop accepting payments after account_expiration_datetime.
  • Unknown ids return 404 with the canonical error shape. The id is the virtual account id (e.g. "va_xxx"), distinct from customer_id and from account_number.

get_wallet_balance

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The path is /wallets/balances/{currency} (plural 'balances' followed by the currency code). A common mistake is /wallets/{currency}/balance — that path returns 404.
  • Only available_balance is returned — there is no separate ledger_balance field. Pending credits are not exposed via this endpoint; treat available_balance as the spendable amount for outbound transfers.
  • Requesting a currency you have not been provisioned for returns 404 — call GET /wallets/balances first to discover the currencies you actually hold.

get_wallet_statement

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • currency is a required query parameter — omitting it returns 400 REQUEST_NOT_VALID. The endpoint returns transactions for exactly one wallet ledger.
  • Pagination is cursor-based, not page-based. Read data.cursor.next from each response and pass it as the 'next' query parameter on the following call. Stop when data.cursor.has_more_items === false.
  • from and to must be valid ISO 8601 date-times (e.g. 2025-04-21T10:55:16Z). Date-only strings (2025-04-21) are typically rejected.
  • transaction_type 'transfer' rows include the embedded 'transfer' object — use it to correlate with /transfers/{id} during reconciliation instead of refetching.

list_bank_branches

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Not every bank has branches (mobile-first and digital-only banks return an empty array). Treat an empty data array as a valid 'no branches required' state, not an error.
  • The id path parameter must be the bank id returned by GET /banks (e.g. bnk_xC78Ibn). Passing a sort code, SWIFT code, or commercial name will return 404 NOT_FOUND.
  • Both swift_code (8 chars) and bic (11 chars, with XXX branch suffix) are returned. Use bic for SWIFT MT103 wires; use code for local routing.

list_banks

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The country query parameter is required. Calling /banks without country returns 400 REQUEST_NOT_VALID with validation_errors[0].field_name == 'country'.
  • Use bank.id (e.g. bnk_cYjd92Qk) when chaining into GET /banks/{id}/branches; use bank.code when building a payout where the destination provider expects the local routing code.
  • Bank lists change rarely — cache the response per country with a 24h TTL to avoid burning a token request on every checkout.

list_chargebacks

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Page size is bounded by the API: 10 minimum, 50 maximum. Defaults to 10.
  • from and to must be ISO 8601 date-times. The most recent chargebacks are on page 1.
  • Each chargeback carries a stage (new/second/pre-arbitration/arbitration/invalid) and a status (pending/accepted/declined/initiated/won/lost/reversed/new). Both must be inspected to know what action is required.
  • Chargebacks are typically initiated by the cardholder via the issuing bank; this endpoint surfaces them so the merchant can respond before due_datetime.

list_charges

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Page size is bounded by the API: 10 minimum, 50 maximum. Requesting size=5 or size=100 returns a 400 error.
  • from and to must be valid ISO 8601 date-times (e.g. 2024-01-01T00:00:00Z). Date-only strings are rejected.
  • Pagination uses meta.page_info.total / current_page / total_pages — not a flat meta.total. Increment page until current_page === total_pages.
  • Each charge in data[] uses created_datetime (not created_at) and embeds nested billing_details, payment_method_details, fees[], settlement_id[], and a discriminated next_action object. Do not assume flat fields.
  • All query parameters are optional. Omit them all to receive the first 10 charges across every status.

list_customers

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Query parameters (page, size) are passed as URL query string parameters, not in the request body. The size parameter must be between 10 and 50; values outside this range return a 400 validation error.
  • Pagination metadata is at meta.page_info (with fields total, current_page, total_pages) — there is no size or per_page field in the page_info object. Maximum 10 items are returned in data per response.
  • Always send an X-Trace-Id header (12-255 chars, e.g. a UUID v4) on every request to make Flutterwave support debugging tractable.
  • Validation error items use the key 'field_name' (not 'field') — match on error.validation_errors[].field_name when surfacing per-field errors to users.

list_fees

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • amount, currency, and payment_method are required query parameters. Optional refinements: card6 (card BIN), country (ISO 3166-1 alpha-2), and network (mobile money operator).
  • The response returns a single object with charge_amount (total to charge the customer) and fee[] (array of {type, amount} breakdown items). Sum fee[].amount to get the total fee.
  • Use this endpoint to surface fee transparency to the customer before they commit to paying. Never hard-code fees — they change by corridor, BIN, and network.

list_mobile_networks

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The country query parameter is mandatory — the mobile network catalogue differs per country. Omitting it returns 400 REQUEST_NOT_VALID with a validation_errors entry on 'country'.
  • Supported countries are restricted to the ISO 3166-1 alpha-2 enum: CG, CM, CI, EG, ET, GA, GH, KE, MW, RW, SN, TZ, TD, UG, ZM. Passing a country outside this set returns 400.
  • Cache the response per country: the operator list changes very rarely and re-fetching on every checkout wastes both a token slot and a /mobile-networks call.

list_orders

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Pagination metadata is nested under meta.page_info with current_page, total_pages, total — not flat meta.page/meta.size/meta.total.
  • Order date field is created_datetime (ISO 8601 with optional sub-second precision), not created_at.
  • Each order item includes a payment_method_details object whose shape varies by the underlying payment method type (card, mobile_money, bank_account, etc.).
  • All query parameters are optional. The size parameter is bounded to [10, 50] — values outside this range will be rejected by the API.
  • Use ISO 8601 date-time strings for from/to (e.g. 2024-01-01T00:00:00Z). Plain date-only values may be rejected.
  • Provide X-Trace-Id (12-255 chars) on every request for distributed-trace correlation in Flutterwave's logs.

list_payment_methods

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Pagination metadata is nested under meta.page_info with current_page, total_pages, total — not flat meta.page/meta.size/meta.total.
  • Each list item is summary-only (id, type, customer_id, created_datetime). Type-specific details (card last4, mobile_money number, etc.) are returned by get_payment_method, not by list.
  • type may include 'bank_transfer' in addition to the create_payment_method enum (card | bank_account | mobile_money | opay | applepay | googlepay | ussd).
  • size is bounded to [10, 50]; values outside the range are rejected. Maximum 10 items are returned per request even at higher page sizes per current docs — check current_page/total_pages to paginate.

list_refunds

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Page size is bounded by the API: 10 minimum, 50 maximum. Defaults to 10.
  • from and to must be ISO 8601 date-times. The most recent refunds are on page 1.
  • Refund objects expose amount_refunded (not amount) and created_datetime (not created_at). reason and status are constrained enums — use the exact strings shown in the schema.

list_settlements

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Page size is bounded by the API: 10 minimum, 50 maximum. Pagination metadata lives at meta.page_info (total, current_page, total_pages).
  • from and to must be ISO 8601 date-times (e.g. 2024-01-01T00:00:00Z). Date-only strings are rejected.
  • Settlements represent the funds moved from your Flutterwave balance to your bank account, not the underlying transactions. Each settlement carries net_amount/gross_amount plus a fees breakdown array.
  • The status field is a fixed enum (disburse-pending, pending, reviewed, approved, completed, completed-offline, failed, flagged, processing, on-hold) — match against these values, not on free-form strings.

list_transfer_recipients

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Pagination is cursor-based and lives at data.cursor (not meta.page_info). Pass back data.cursor.next as `next=` or data.cursor.previous as `previous=`; use data.cursor.has_more_items to know when to stop. Do not synthesize cursor values client-side.
  • size must be between 10 and 50 inclusive. Values outside this range return a 400 REQUEST_NOT_VALID validation error.
  • Recipients are discriminated by the `type` field (bank | mobile_money | wallet | cash_pickup | crypto). Type-specific destination details live on sibling keys (bank, mobile_money, wallet, cash_pickup, crypto) — do not assume a single `details` object.
  • Recipient IDs returned here are opaque strings. Pass them as-is to get/delete_transfer_recipient — do not apply encodeURIComponent.

list_transfer_senders

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • This list endpoint uses cursor-based pagination via data.cursor.next / data.cursor.previous — pass the cursor string back as the next= or previous= query parameter. Do not synthesize cursor values client-side.
  • size must be between 10 and 50 inclusive (default 10). Values outside this range return a 400 REQUEST_NOT_VALID error.
  • Senders are returned under data.senders (an array), not data (which is an object containing both cursor and senders).
  • Sender IDs returned here are opaque strings. Pass them as-is to get/delete_transfer_sender — do not apply encodeURIComponent.

list_transfers

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Pagination is cursor-based, not page-based. Paginate by reading data.cursor.next from each response and passing it as the 'next' query parameter on the following call. Stop when data.cursor.has_more_items === false.
  • Status filter accepts lowercase values (new, pending, failed, successful, cancelled) but the returned 'status' field is uppercase (NEW, PENDING, ...). Compare case-insensitively when filtering client-side.
  • size is bounded to 10–50; values outside this range return 400 REQUEST_NOT_VALID.
  • transfer.amount.applies_to indicates which currency the value is denominated in — read source_currency and destination_currency together with amount.applies_to before reconciling.

list_virtual_accounts

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • size must be between 10 and 50 inclusive — values outside this range return 400. page is 1-indexed; passing 0 also returns 400.
  • from/to must be valid ISO 8601 timestamps with timezone (e.g. 2025-04-21T10:55:16Z). Naive dates may be rejected.
  • Account expiry is returned as `account_expiration_datetime` (ISO 8601), not `expiry`. account_type is `static` (reusable, fixed account number) or `dynamic` (one-time per transaction).
  • Listing returns accounts across all customers and currencies — filter client-side by `currency` or via `reference`. Virtual accounts are most commonly used in Nigeria (NGN) where customers initiate bank transfers to a unique generated account.

list_wallet_balances

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Each entry contains only 'currency' and 'available_balance' — there is no ledger_balance or updated_at field. Treat available_balance as the spendable amount.
  • The response only includes currencies you have been provisioned for. A new account may return an empty data array — that is not an error.
  • Sum available_balance per currency only, never across currencies. Cross-currency totals require an FX conversion the API does not perform.

lookup_bank_account

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The request body is discriminated by currency. NGN expects {currency:'NGN', account:{code, number}}. GBP and USD have different nested shapes (GBP needs type+name|business_name; USD needs account.country='NG'). Sending an NGN-shaped body for a GBP lookup returns 400 REQUEST_NOT_VALID.
  • Always confirm the returned account_name with the user before triggering a payout. Account-name spoofing is the most common social-engineering attack on payout flows.
  • Headers X-Trace-Id (12-255 chars) and X-Scenario-Key (1-1000 chars, sandbox only) are optional. Use X-Scenario-Key in sandbox to simulate auth_redirect / failed_lookup behaviours.

lookup_wallet_account

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Despite the legacy name 'lookup_wallet_account', this endpoint only resolves a merchant identifier within a payment provider (currently 'flutterwave'). It does NOT resolve mobile-money MSISDNs — for E.164 phone resolution against MTN/Safaricom/etc. use the bank/mobile-money lookup endpoints in the transfer creation flow.
  • The request body must include both 'provider' and 'identifier'. Omitting either returns 400 REQUEST_NOT_VALID with validation_errors on the missing field.
  • Always show the resolved 'name' to the user before initiating a payout so the recipient is confirmed — name mismatches are the single largest source of payout disputes.

retry_transfer

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Two distinct retry modes via the `action` field: `retry` only works on transfers in a FAILED state (re-attempts the same payment); `duplicate` only works on transfers in a SUCCESSFUL state (creates a brand-new transfer with the same details). Using `retry` on a successful transfer or `duplicate` on a failed one returns 409 RESOURCE_CONFLICT.
  • X-Idempotency-Key is strongly recommended (12-255 chars) to prevent accidental double-submissions on flaky networks — replays return the original response instead of creating another attempt.
  • The path is POST /transfers/{id}/retries (plural "retries") — not /transfers/{id}/retry. The response returns a NEW transfer id distinct from the parent id; the `retry` or `duplicate` object in the response carries the parent_id and parent_reference linking back.
  • Always pair this call with a subsequent GET /transfers/{id} poll using the new id to confirm the new attempt's terminal status before acting on it. The 201 response only reflects the initial submission, not the final outcome.
  • X-Trace-Id is required on this endpoint (12-255 chars). Transfer IDs are opaque strings — pass as-is into the path, do not apply encodeURIComponent.

search_customers

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Despite being a read operation, search uses POST with a JSON body — GET /customers does NOT accept search filters. The request body shape is { "email": "..." } only; email is the sole documented search field and it is required.
  • Email value must satisfy the pattern ^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$. A 400 with error.type 'REQUEST_NOT_VALID' is returned for malformed values.
  • Pagination uses URL query parameters (?page=&size=), not body fields. size must be in [10, 50]; values outside that range return HTTP 400 with a validation error.
  • The pagination meta object only contains total, current_page and total_pages — there is no 'size' or 'per_page' field in meta.page_info. Maximum 10 items are returned in data per response.
  • Always send an X-Trace-Id header (12-255 chars) so Flutterwave support can find the request in their logs.
  • Validation error items use the key 'field_name' (not 'field') — match on error.validation_errors[].field_name when surfacing per-field errors to users.

update_charge

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • authorization is an OBJECT, not a string. Set authorization.type to one of otp | pin | external_3ds | avs and provide the matching sub-object: { type: "otp", otp: { code } } / { type: "pin", pin: { nonce, encrypted_pin } } / { type: "external_3ds", external_3ds: { ... } } / { type: "avs", avs: { address: { ... } } }. This is the primary use-case for update_charge — resolving a pending next_action.
  • authorization.type=pin requires encrypted_pin obtained from Flutterwave's field-level encryption flow plus a 12-char alphanumeric nonce. Never send a raw PIN — the request will be rejected.
  • Only meta and authorization can be PUT — the body is restricted to those two fields. amount, currency, customer_id, payment_method_id and reference are immutable once the charge is created.
  • Update only makes sense on a pending charge. Updating a charge that has already reached a terminal status (succeeded, failed, voided) returns 409 Conflict.
  • meta values must be strings (additionalProperties: string). Pass numbers/booleans as strings if you need to store them.
  • The response is the full charge object — same shape as get_charge — with the new processor_response and possibly an updated next_action. Re-check data.status to know whether the customer still has work to do.

update_chargeback

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • On update, status only accepts the enum values accepted or declined. Other final states (won/lost/reversed) are set by the system, not by the merchant.
  • Two ways to attach proof: uploaded_proof (URL to an already-hosted document) or proof_data (base64-encoded document body). Pick one — do not send both.
  • Status transitions are typically one-way once accepted or declined; inspect the response.data.status to confirm the new state before notifying the customer.

update_customer

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • Email is NOT updatable via this endpoint — the PUT body only accepts name, phone, address and meta. To change a customer's email, create a new customer record.
  • Always send an X-Trace-Id header (12-255 chars). X-Idempotency-Key is also recommended on PUT /customers/{id} so a retried request after a network error does not double-apply the update.
  • Customer IDs are opaque strings (e.g. cus_3XarBILKQS). Do not apply encodeURIComponent to the id when constructing the URL — pass it as-is.
  • Sending a 'meta' object replaces the full existing meta object — there is no per-key merge. Read the customer first, then send the merged meta to avoid losing existing keys.
  • name.first / name.middle / name.last must each match ^(?![ ,.'-]*$)[A-Za-z ,.'-]{2,50}$ (letters, spaces, commas, periods, apostrophes, hyphens; 2-50 chars; cannot be only punctuation). phone.country_code: 1-3 digits, phone.number: 7-10 digits, both digits only. address.country: uppercase ISO 3166-1 alpha-2.
  • Validation error items use the key 'field_name' (not 'field') — match on error.validation_errors[].field_name when surfacing per-field errors to users.

update_order

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • The PUT body accepts only two documented fields: action ('void' | 'capture') and meta. There is no documented authorization payload on update — supply 3DS / SCA at create_order time instead.
  • Terminal statuses (completed, voided, failed) typically reject further updates with 10409 RESOURCE_CONFLICT.
  • action='capture' is only valid for orders currently in the 'authorized' status (manual-capture flows); action='void' cancels an authorization before capture.

update_transfer

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • This endpoint can only be used to update instructions about a deferred (or scheduled) payout. Use 'initiate: true' to trigger it and 'close: true' to cancel it. Once a transfer has moved past the deferred/scheduled state it cannot be updated and the call returns 409 RESOURCE_CONFLICT.
  • Do NOT attempt to send 'narration', 'meta', amount, recipient, sender, currency, or any field of payment_instruction in the body — only initiate, close, and disburse_option are accepted. Sending unsupported fields returns 400 REQUEST_NOT_VALID.
  • disburse_option.date_time uses the 'YYYY-MM-DD HH:MM:SS' format (note the space, not 'T'), paired with an IANA timezone string in disburse_option.timezone. Pass both together when rescheduling.

update_virtual_account

  • Flutterwave v4 uses OAuth2 client credentials. POST client_id and client_secret as form-encoded body to https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token to obtain an access_token (valid 10 minutes), then use it as Bearer in the Authorization header. The FLUTTERWAVE_CLIENT_CREDENTIALS env var stores base64(client_id:client_secret).
  • PUT /virtual-accounts/{id} is NOT a generic update. The body MUST include action_type — either "update_bvn" or "update_status". You cannot change amount, expiry, narration, customer_id or currency via this endpoint.
  • When action_type="update_status", only status="inactive" is accepted (i.e. you can only deactivate). Reactivation must be requested through Flutterwave support.
  • When action_type="update_bvn", bvn must be exactly 11 digits matching ^[1-9][0-9]{10}$. BVN updates are Nigeria-specific.
  • If you need to change the expected amount or narration, create a new virtual account instead — update_virtual_account does not support those changes.

webhook_event

  • The signature header is 'flutterwave-signature' (v4). The old v3 header 'verif-hash' is no longer used.
  • Flutterwave signs the raw request body with HMAC-SHA256 and encodes the result as base64 — not hex. Compute: `crypto.createHmac('sha256', secret).update(rawBody).digest('base64')` and compare with the 'flutterwave-signature' header. A hex digest will never match.
  • Always use the raw request body (string or Buffer) for HMAC computation — never re-serialise parsed JSON, as key order may change and break verification.
  • Use a constant-time comparison (e.g. node:crypto timingSafeEqual) instead of === to prevent timing-side-channel attacks.
  • The top-level event id field differs by type: charge.completed uses 'id', while refund.completed, transfer.disburse, and transfer.reversal use 'webhook_id'. Always read whichever is present for deduplication.
  • Always re-verify the resource server-side via the corresponding GET endpoint before fulfilling or adjusting ledgers — never trust webhook payload data alone.
  • Deduplicate on the event id/webhook_id — Flutterwave may redeliver the same event after a transient failure on your side.
  • Respond with HTTP 200 within 60 seconds. Any non-2xx response or timeout triggers automatic retries (3 attempts at 30-minute intervals). Offload heavy processing to a background job.
  • data.status for charge.completed is not always 'succeeded' — failed and voided charges also trigger the event. Always check the status field explicitly.
  • The payload schemas for transfer.disburse, transfer.reversal, and order.authorization are documented from the Flutterwave reference but not verified against live deliveries — treat additionalProperties as likely.
  • Exclude your webhook endpoint from CSRF protection. Frameworks like Next.js (middleware), Django, Rails, and Laravel apply CSRF checks globally — incoming POST requests from Flutterwave carry no CSRF token and will be rejected with 403 unless the route is explicitly exempt.

Details

Category
payment
Sandbox
Yes