flutterwave

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

flutterwave

flutterwave

The complete, production-ready AI agent skill for integrating the Flutterwave API. Natively supports both v3 (Production Stable) and v4 (Next-Gen Public Beta) so developers and AI agents can choose and switch versions seamlessly without third-party CLI or proxy dependencies.

用于集成 Flutterwave API 的完整、生产就绪 AI Agent skill。原生支持 v3(Production Stable,生产稳定版) 和 v4(Next-Gen Public Beta,下一代公开测试版),让开发者和 AI Agent 可以无缝选择并切换版本,无需依赖第三方 CLI 或代理。

Architecture & Version Comparison

架构与版本对比

Featurev3 (Production Stable)v4 (Next-Gen Public Beta)
StatusGlobally deployed, battle-testedPublic Beta (high performance, microservices)
Base URL
https://api.flutterwave.com/v3
Sandbox:
https://developersandbox-api.flutterwave.com
<br>Live:
https://f4bexperience.flutterwave.com/
AuthenticationStatic Secret Key Bearer (
FLW_SECRET_KEY
)
OAuth 2.0 OpenID Connect Client Credentials (
client_id
+
client_secret
)
Token EndpointN/A
POST https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token
Tracing HeaderOptional
X-Trace-Id
(12–255 char UUID, required)
IdempotencyCustom
tx_ref
in payload
X-Idempotency-Key
header +
reference
in payload
Primary Inflow
POST /payments
or
POST /charges
POST /charges
with
customer_id
and
payment_method_id
Primary Outflow
POST /transfers
with account details
POST /transfers
with
payment_instruction
&
recipient_id

特性v3(Production Stable,生产稳定版)v4(Next-Gen Public Beta,下一代公开测试版)
状态全球部署,经过实战验证公开测试版(高性能、微服务)
基础 URL
https://api.flutterwave.com/v3
Sandbox(沙箱):
https://developersandbox-api.flutterwave.com
<br>Live(生产环境):
https://f4bexperience.flutterwave.com/
身份验证静态 Secret Key Bearer(
FLW_SECRET_KEY
)
OAuth 2.0 OpenID Connect Client Credentials(
client_id
+
client_secret
)
Token 端点不适用
POST https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token
追踪请求头可选
X-Trace-Id
(12–255 字符 UUID,必填)
幂等性请求体中使用自定义
tx_ref
X-Idempotency-Key
请求头 + 请求体中的
reference
主要资金流入
POST /payments
或
POST /charges
POST /charges
,包含
customer_id
和
payment_method_id
主要资金流出
POST /transfers
,包含账户信息
POST /transfers
,包含
payment_instruction
和
recipient_id

Universal Client Implementation (TypeScript)

通用客户端实现(TypeScript)

Zero external CLI dependencies. Provides full dual-version execution, automatic v4 OAuth 2.0 token caching, and request tracing.
ts
import crypto from "node:crypto";

export interface FlutterwaveConfig {
  secretKey?: string; // v3 secret key (e.g., FLWSECK_TEST-... or FLWSECK-...)
  encryptionKey?: string; // v3 3DES encryption key for direct card charges
  v4ClientId?: string; // v4 OAuth2 client_id
  v4ClientSecret?: string; // v4 OAuth2 client_secret
  v3BaseUrl?: string; // default: https://api.flutterwave.com/v3
  v4BaseUrl?: string; // sandbox: https://developersandbox-api.flutterwave.com | live: https://f4bexperience.flutterwave.com/
  v4IdpUrl?: string; // default: https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token
}

export interface FlwResponse<T = unknown> {
  status: "success" | "error";
  message: string;
  data: T;
  meta?: Record<string, unknown>;
}

export class FlutterwaveApiError extends Error {
  constructor(
    message: string,
    public readonly statusCode: number,
    public readonly payload?: unknown,
  ) {
    super(message);
    this.name = "FlutterwaveApiError";
  }
}

export class Flutterwave {
  private readonly config: FlutterwaveConfig;
  private v4TokenCache: { token: string; expiresAt: number } | null = null;

  constructor(config: FlutterwaveConfig = {}) {
    this.config = {
      secretKey: config.secretKey ?? process.env.FLW_SECRET_KEY,
      encryptionKey: config.encryptionKey ?? process.env.FLW_ENCRYPTION_KEY,
      v4ClientId: config.v4ClientId ?? process.env.FLW_V4_CLIENT_ID,
      v4ClientSecret: config.v4ClientSecret ?? process.env.FLW_V4_CLIENT_SECRET,
      v3BaseUrl: config.v3BaseUrl ?? process.env.FLW_V3_BASE_URL ?? "https://api.flutterwave.com/v3",
      v4BaseUrl: config.v4BaseUrl ?? process.env.FLW_V4_BASE_URL ?? "https://developersandbox-api.flutterwave.com",
      v4IdpUrl: config.v4IdpUrl ?? "https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token",
    };
  }

  /**
   * Retrieves or refreshes OAuth 2.0 access token for v4 requests.
   */
  private async getV4Token(): Promise<string> {
    const now = Date.now();
    if (this.v4TokenCache && this.v4TokenCache.expiresAt > now + 60_000) {
      return this.v4TokenCache.token;
    }

    if (!this.config.v4ClientId || !this.config.v4ClientSecret) {
      throw new Error("Missing v4 credentials: FLW_V4_CLIENT_ID and FLW_V4_CLIENT_SECRET are required for v4 calls.");
    }

    const res = await fetch(this.config.v4IdpUrl!, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "client_credentials",
        client_id: this.config.v4ClientId,
        client_secret: this.config.v4ClientSecret,
      }).toString(),
    });

    if (!res.ok) {
      throw new FlutterwaveApiError(`Failed to fetch v4 OAuth token: ${res.statusText}`, res.status);
    }

    const data = (await res.json()) as { access_token: string; expires_in: number };
    this.v4TokenCache = {
      token: data.access_token,
      expiresAt: now + data.expires_in * 1000,
    };

    return data.access_token;
  }

  /**
   * Executes a typed API request to either v3 or v4.
   */
  async request<T = unknown>(
    version: "v3" | "v4",
    path: string,
    options: RequestInit & { idempotencyKey?: string } = {},
  ): Promise<FlwResponse<T>> {
    const { idempotencyKey, headers: customHeaders, ...init } = options;
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
      ...(customHeaders as Record<string, string>),
    };

    let baseUrl: string;
    if (version === "v3") {
      if (!this.config.secretKey) {
        throw new Error("Missing FLW_SECRET_KEY for v3 request");
      }
      baseUrl = this.config.v3BaseUrl!;
      headers["Authorization"] = `Bearer ${this.config.secretKey}`;
    } else {
      baseUrl = this.config.v4BaseUrl!;
      const token = await this.getV4Token();
      headers["Authorization"] = `Bearer ${token}`;
      headers["X-Trace-Id"] = crypto.randomUUID();
      if (idempotencyKey) {
        headers["X-Idempotency-Key"] = idempotencyKey;
      }
    }

    const url = `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
    const response = await fetch(url, { ...init, headers });
    const body = (await response.json()) as FlwResponse<T>;

    if (!response.ok || body.status === "error") {
      throw new FlutterwaveApiError(
        body.message || `Flutterwave ${version} request failed with status ${response.status}`,
        response.status,
        body,
      );
    }

    return body;
  }
}

无外部 CLI 依赖。提供完整的双版本执行能力、自动缓存 v4 OAuth 2.0 token 和请求追踪功能。
ts
import crypto from "node:crypto";

export interface FlutterwaveConfig {
  secretKey?: string; // v3 secret key (e.g., FLWSECK_TEST-... or FLWSECK-...)
  encryptionKey?: string; // v3 3DES encryption key for direct card charges
  v4ClientId?: string; // v4 OAuth2 client_id
  v4ClientSecret?: string; // v4 OAuth2 client_secret
  v3BaseUrl?: string; // default: https://api.flutterwave.com/v3
  v4BaseUrl?: string; // sandbox: https://developersandbox-api.flutterwave.com | live: https://f4bexperience.flutterwave.com/
  v4IdpUrl?: string; // default: https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token
}

export interface FlwResponse<T = unknown> {
  status: "success" | "error";
  message: string;
  data: T;
  meta?: Record<string, unknown>;
}

export class FlutterwaveApiError extends Error {
  constructor(
    message: string,
    public readonly statusCode: number,
    public readonly payload?: unknown,
  ) {
    super(message);
    this.name = "FlutterwaveApiError";
  }
}

export class Flutterwave {
  private readonly config: FlutterwaveConfig;
  private v4TokenCache: { token: string; expiresAt: number } | null = null;

  constructor(config: FlutterwaveConfig = {}) {
    this.config = {
      secretKey: config.secretKey ?? process.env.FLW_SECRET_KEY,
      encryptionKey: config.encryptionKey ?? process.env.FLW_ENCRYPTION_KEY,
      v4ClientId: config.v4ClientId ?? process.env.FLW_V4_CLIENT_ID,
      v4ClientSecret: config.v4ClientSecret ?? process.env.FLW_V4_CLIENT_SECRET,
      v3BaseUrl: config.v3BaseUrl ?? process.env.FLW_V3_BASE_URL ?? "https://api.flutterwave.com/v3",
      v4BaseUrl: config.v4BaseUrl ?? process.env.FLW_V4_BASE_URL ?? "https://developersandbox-api.flutterwave.com",
      v4IdpUrl: config.v4IdpUrl ?? process.env.FLW_V4_IDP_URL ?? "https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token",
    };
  }

  /**
   * Retrieves or refreshes OAuth 2.0 access token for v4 requests.
   */
  private async getV4Token(): Promise<string> {
    const now = Date.now();
    if (this.v4TokenCache && this.v4TokenCache.expiresAt > now + 60_000) {
      return this.v4TokenCache.token;
    }

    if (!this.config.v4ClientId || !this.config.v4ClientSecret) {
      throw new Error("Missing v4 credentials: FLW_V4_CLIENT_ID and FLW_V4_CLIENT_SECRET are required for v4 calls.");
    }

    const res = await fetch(this.config.v4IdpUrl!, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "client_credentials",
        client_id: this.config.v4ClientId,
        client_secret: this.config.v4ClientSecret,
      }).toString(),
    });

    if (!res.ok) {
      throw new FlutterwaveApiError(`Failed to fetch v4 OAuth token: ${res.statusText}`, res.status);
    }

    const data = (await res.json()) as { access_token: string; expires_in: number };
    this.v4TokenCache = {
      token: data.access_token,
      expiresAt: now + data.expires_in * 1000,
    };

    return data.access_token;
  }

  /**
   * Executes a typed API request to either v3 or v4.
   */
  async request<T = unknown>(
    version: "v3" | "v4",
    path: string,
    options: RequestInit & { idempotencyKey?: string } = {},
  ): Promise<FlwResponse<T>> {
    const { idempotencyKey, headers: customHeaders, ...init } = options;
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
      ...(customHeaders as Record<string, string>),
    };

    let baseUrl: string;
    if (version === "v3") {
      if (!this.config.secretKey) {
        throw new Error("Missing FLW_SECRET_KEY for v3 request");
      }
      baseUrl = this.config.v3BaseUrl!;
      headers["Authorization"] = `Bearer ${this.config.secretKey}`;
    } else {
      baseUrl = this.config.v4BaseUrl!;
      const token = await this.getV4Token();
      headers["Authorization"] = `Bearer ${token}`;
      headers["X-Trace-Id"] = crypto.randomUUID();
      if (idempotencyKey) {
        headers["X-Idempotency-Key"] = idempotencyKey;
      }
    }

    const url = `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
    const response = await fetch(url, { ...init, headers });
    const body = (await response.json()) as FlwResponse<T>;

    if (!response.ok || body.status === "error") {
      throw new FlutterwaveApiError(
        body.message || `Flutterwave ${version} request failed with status ${response.status}`,
        response.status,
        body,
      );
    }

    return body;
  }
}

Popular Actions & Version Matrix

常用操作与版本对照表

Any developer or AI agent using this skill can execute any operation in either v3 or v4:
ActionMethodv3 Routev4 Beta RouteKey Parameters / Description
Standard Hosted Checkout
POST
/payments
/charges
tx_ref
,
amount
,
currency
,
redirect_url
,
customer: { email, name }
. Returns checkout URL.
Verify Transaction (by ID)
GET
/transactions/{id}/verify
/charges/{id}
Confirms final payment status, settled amount, and card token. Mandatory before order fulfillment.
Verify Transaction (by ref)
GET
/transactions/verify_by_reference?tx_ref=...
/charges?reference=...
Queries payment using the merchant's custom order/transaction reference.
List Transactions
GET
/transactions
/charges
Filters by
from
,
to
,
status
,
currency
, and pagination
page
.
Direct Card Charge
POST
/charges?type=card
/charges
v3 uses 3DES encrypted payload; v4 uses
payment_method_id
with
authorization
.
Validate Charge (OTP)
POST
/validate-charge
/charges/{id}/validate
Submits customer OTP challenge code (
flw_ref
,
otp
).
Bank Transfer Collection
POST
/charges?type=bank_transfer
/virtual-accounts
Generates dynamic dedicated Nigerian NIP bank account with expiry timer.
Mobile Money (M-Pesa)
POST
/charges?type=mpesa
/charges
Triggers instant STK push prompt on customer mobile device in Kenya (
KES
).
Mobile Money (Ghana)
POST
/charges?type=mobile_money_ghana
/charges
Charges MTN, Telecel (Vodafone voucher), or AirtelTigo wallets (
GHS
).
USSD Payment
POST
/charges?type=ussd
/charges
Returns bank-specific interactive dial code (e.g.
*737*...#
for GTBank) for offline payments.
Tokenized Charge
POST
/tokenized-charges
/charges
(
recurring: true
)
Re-bills stored
card.token
for 1-click checkout and subscriptions without card entry.
Initiate Transfer (Payout)
POST
/transfers
/transfers
Disburses funds to bank accounts or mobile wallets. v4 uses
payment_instruction
+
action
.
Get Transfer Status
GET
/transfers/{id}
/transfers/{id}
Inspects payout status (
SUCCESSFUL
,
FAILED
,
PENDING
).
Retry Transfer
POST
/transfers/{id}/retries
/transfers/{id}/retry
Resubmits failed transfer for reprocessing.
List Beneficiaries
GET
/beneficiaries
/transfer-recipients
Lists saved bank account and mobile wallet payout contacts.
Create Beneficiary
POST
/beneficiaries
/transfer-recipients
Saves recipient (
account_number
,
account_bank
,
beneficiary_name
,
currency
).
Create Virtual Account
POST
/virtual-account-numbers
/virtual-accounts
Provisions permanent or dynamic virtual bank accounts for direct customer wallet funding.
Bulk Virtual Accounts
POST
/bulk-virtual-account-numbers
/virtual-accounts
Batches creation of dedicated virtual accounts for fintech platforms.
Create Subaccount (Split)
POST
/subaccounts
Split configurationConfigures vendor subaccount for marketplace splits (
split_type: "percentage" | "flat"
).
Create Payment Plan
POST
/payment-plans
Subscription plansSets automated subscription billing intervals (
daily
,
weekly
,
monthly
,
yearly
).
Create Virtual Card
POST
/virtual-cards
Card issuingIssues digital dollar (
USD
) or local currency debit cards with billing address.
Resolve Bank Account
POST
/accounts/resolve
/accounts/lookup
Verifies Nigerian bank account number and resolves official registered account holder name.
Validate BVN
POST
/bvn/match
KYC validationConfirms Bank Verification Number matches customer legal identity for compliance.
Issue Refund
POST
/transactions/{id}/refund
/refunds
Issues full or partial refunds directly to customer's source account.
Manage Chargebacks
GET
/
PUT
/chargebacks
/chargebacks
Queries open dispute claims and submits defense evidence before the due date.
List Settlements
GET
/settlements
/settlement
Reconciles bank deposits, gross transactions, and processor fee deductions.
Pay Bills & Airtime
POST
/bills
Utility billsPurchases airtime, mobile data bundles, power tokens, and renews cable TV subscriptions.

使用此 skill 的开发者或 AI Agent 均可在 v3 或 v4 中执行任意操作:
操作方法v3 路由v4 Beta 路由关键参数/说明
标准托管结账
POST
/payments
/charges
tx_ref
、
amount
、
currency
、
redirect_url
、
customer: { email, name }
。返回 checkout URL。
验证交易(按 ID)
GET
/transactions/{id}/verify
/charges/{id}
确认最终支付状态、结算金额和银行卡 token。订单履约前必须执行。
验证交易(按 ref)
GET
/transactions/verify_by_reference?tx_ref=...
/charges?reference=...
使用商户自定义的订单或交易 reference 查询付款。
交易列表
GET
/transactions
/charges
支持按
from
、
to
、
status
、
currency
过滤,并支持分页参数
page
。
银行卡直接扣款
POST
/charges?type=card
/charges
v3 使用 3DES 加密请求体;v4 使用
payment_method_id
和
authorization
。
验证扣款(OTP)
POST
/validate-charge
/charges/{id}/validate
提交客户 OTP 挑战验证码(
flw_ref
、
otp
)。
银行转账收款
POST
/charges?type=bank_transfer
/virtual-accounts
生成带到期计时器的尼日利亚 NIP 动态专用银行账户。
移动支付(M-Pesa)
POST
/charges?type=mpesa
/charges
在肯尼亚客户移动设备上触发即时 STK push 提示(
KES
)。
移动支付(加纳)
POST
/charges?type=mobile_money_ghana
/charges
从 MTN、Telecel(Vodafone voucher)或 AirtelTigo 钱包扣款(
GHS
)。
USSD 支付
POST
/charges?type=ussd
/charges
返回银行专属的交互式拨号代码(例如 GTBank 的
*737*...#
),用于离线支付。
Token 化扣款
POST
/tokenized-charges
/charges
(
recurring: true
)
使用已保存的
card.token
重新扣款,支持一键结账和订阅,无需再次输入银行卡信息。
发起转账(付款)
POST
/transfers
/transfers
向银行账户或移动钱包拨付资金。v4 使用
payment_instruction
+
action
。
获取转账状态
GET
/transfers/{id}
/transfers/{id}
查看付款状态(
SUCCESSFUL
、
FAILED
、
PENDING
)。
重试转账
POST
/transfers/{id}/retries
/transfers/{id}/retry
重新提交失败的转账以供再次处理。
受益人列表
GET
/beneficiaries
/transfer-recipients
列出已保存的银行账户和移动钱包付款联系人。
创建受益人
POST
/beneficiaries
/transfer-recipients
保存收款人(
account_number
、
account_bank
、
beneficiary_name
、
currency
)。
创建虚拟账户
POST
/virtual-account-numbers
/virtual-accounts
开通永久或动态虚拟银行账户,供客户钱包直接充值。
批量虚拟账户
POST
/bulk-virtual-account-numbers
/virtual-accounts
为 fintech 平台批量创建专用虚拟账户。
创建子账户(分账)
POST
/subaccounts
分账配置为 marketplace 分账配置商户子账户(
split_type: "percentage" | "flat"
)。
创建支付计划
POST
/payment-plans
订阅计划设置自动订阅扣款周期(
daily
、
weekly
、
monthly
、
yearly
)。
创建虚拟卡
POST
/virtual-cards
发卡签发数字美元(
USD
)或本地货币借记卡,并关联账单地址。
解析银行账户
POST
/accounts/resolve
/accounts/lookup
验证尼日利亚银行账号,并解析官方登记的账户持有人姓名。
验证 BVN
POST
/bvn/match
KYC 验证确认 Bank Verification Number 与客户法定身份一致,以满足合规要求。
发起退款
POST
/transactions/{id}/refund
/refunds
直接向客户原始账户发放全额或部分退款。
管理拒付
GET
/
PUT
/chargebacks
/chargebacks
查询未结争议,并在截止日期前提交抗辩证据。
结算记录列表
GET
/settlements
/settlement
对账银行存款、交易总额和支付处理商费用扣除。
支付账单与购买话费
POST
/bills
公用事业账单购买话费、移动流量包、用电代扣券,并续订有线电视服务。

Action Code Recipes

操作代码示例

1. Create a Standard Checkout Payment Link (v3)

1. 创建标准托管结账支付链接(v3)

ts
const flw = new Flutterwave();

const session = await flw.request<{ link: string }>("v3", "/payments", {
  method: "POST",
  body: JSON.stringify({
    tx_ref: `ORD-${Date.now()}`,
    amount: "7500.00",
    currency: "NGN",
    redirect_url: "https://yourdomain.com/checkout/callback",
    customer: {
      email: "buyer@example.com",
      name: "Amina Bello",
      phonenumber: "+2348012345678",
    },
    customizations: {
      title: "Store Checkout",
      description: "Payment for order #1082",
    },
  }),
});

console.log("Customer Payment URL:", session.data.link);
ts
const flw = new Flutterwave();

const session = await flw.request<{ link: string }>("v3", "/payments", {
  method: "POST",
  body: JSON.stringify({
    tx_ref: `ORD-${Date.now()}`,
    amount: "7500.00",
    currency: "NGN",
    redirect_url: "https://yourdomain.com/checkout/callback",
    customer: {
      email: "buyer@example.com",
      name: "Amina Bello",
      phonenumber: "+2348012345678",
    },
    customizations: {
      title: "Store Checkout",
      description: "Payment for order #1082",
    },
  }),
});

console.log("Customer Payment URL:", session.data.link);

2. Mandatory Server-Side Transaction Verification (v3 & v4)

2. 服务端交易验证(必需,v3 和 v4)

ts
const flw = new Flutterwave();

// v3 verification by Transaction ID
const v3Tx = await flw.request<{
  status: string;
  amount: number;
  currency: string;
  tx_ref: string;
  card?: { token: string };
}>("v3", `/transactions/${transactionId}/verify`, { method: "GET" });

if (v3Tx.data.status === "successful" && v3Tx.data.amount >= orderAmount) {
  // Safe to credit account or ship goods
  const savedCardToken = v3Tx.data.card?.token;
}

// v4 Next-Gen verification by Charge ID
const v4Charge = await flw.request<{
  status: string;
  amount: number;
  currency: string;
  reference: string;
}>("v4", `/charges/${chargeId}`, { method: "GET" });
ts
const flw = new Flutterwave();

// v3 verification by Transaction ID
const v3Tx = await flw.request<{
  status: string;
  amount: number;
  currency: string;
  tx_ref: string;
  card?: { token: string };
}>("v3", `/transactions/${transactionId}/verify`, { method: "GET" });

if (v3Tx.data.status === "successful" && v3Tx.data.amount >= orderAmount) {
  // Safe to credit account or ship goods
  const savedCardToken = v3Tx.data.card?.token;
}

// v4 Next-Gen verification by Charge ID
const v4Charge = await flw.request<{
  status: string;
  amount: number;
  currency: string;
  reference: string;
}>("v4", `/charges/${chargeId}`, { method: "GET" });

3. Initiate Instant Bank Transfer / Payout (v3 & v4)

3. 发起即时银行转账/付款(v3 和 v4)

ts
const flw = new Flutterwave();

// v3 Payout
const v3Payout = await flw.request("v3", "/transfers", {
  method: "POST",
  body: JSON.stringify({
    account_bank: "058", // GTBank
    account_number: "0123456789",
    amount: 50000,
    currency: "NGN",
    reference: `PAYOUT-${Date.now()}`,
    narration: "Vendor disbursement",
  }),
});

// v4 Next-Gen Transfer Orchestrator
const v4Payout = await flw.request("v4", "/transfers", {
  method: "POST",
  idempotencyKey: `IDEMP-${Date.now()}`,
  body: JSON.stringify({
    action: "instant",
    reference: `V4-PAYOUT-${Date.now()}`,
    narration: "Vendor payment",
    payment_instruction: {
      amount: 50000,
      currency: "NGN",
      recipient_id: "rcb_B9aAgsdzzl",
    },
  }),
});

ts
const flw = new Flutterwave();

// v3 Payout
const v3Payout = await flw.request("v3", "/transfers", {
  method: "POST",
  body: JSON.stringify({
    account_bank: "058", // GTBank
    account_number: "0123456789",
    amount: 50000,
    currency: "NGN",
    reference: `PAYOUT-${Date.now()}`,
    narration: "Vendor disbursement",
  }),
});

// v4 Next-Gen Transfer Orchestrator
const v4Payout = await flw.request("v4", "/transfers", {
  method: "POST",
  idempotencyKey: `IDEMP-${Date.now()}`,
  body: JSON.stringify({
    action: "instant",
    reference: `V4-PAYOUT-${Date.now()}`,
    narration: "Vendor payment",
    payment_instruction: {
      amount: 50000,
      currency: "NGN",
      recipient_id: "rcb_B9aAgsdzzl",
    },
  }),
});

Webhook Ingestion & Security Standard

Webhook 接收与安全标准

Flutterwave verifies webhooks using a secret hash configured in your Flutterwave Dashboard (Settings > Webhooks) and passed in the
verif-hash
HTTP header:
ts
import crypto from "node:crypto";

export function handleFlutterwaveWebhook(req: Request) {
  const incomingHash = req.headers.get("verif-hash");
  const secretHash = process.env.FLW_WEBHOOK_HASH;

  if (!incomingHash || !secretHash) {
    return new Response("Unauthorized", { status: 401 });
  }

  // Timing-safe constant-time comparison to prevent side-channel leaks
  const a = Buffer.from(incomingHash, "utf8");
  const b = Buffer.from(secretHash, "utf8");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return new Response("Forbidden", { status: 403 });
  }

  // Respond HTTP 200 immediately to meet Flutterwave's 60-second SLA
  // Process the event asynchronously in the background
  return new Response(JSON.stringify({ status: "success" }), { status: 200 });
}

Flutterwave 使用在 Flutterwave Dashboard(Settings > Webhooks)中配置、并通过
verif-hash
HTTP 请求头传入的 secret hash 验证 webhook:
ts
import crypto from "node:crypto";

export function handleFlutterwaveWebhook(req: Request) {
  const incomingHash = req.headers.get("verif-hash");
  const secretHash = process.env.FLW_WEBHOOK_HASH;

  if (!incomingHash || !secretHash) {
    return new Response("Unauthorized", { status: 401 });
  }

  // Timing-safe constant-time comparison to prevent side-channel leaks
  const a = Buffer.from(incomingHash, "utf8");
  const b = Buffer.from(secretHash, "utf8");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return new Response("Forbidden", { status: 403 });
  }

  // Respond HTTP 200 immediately to meet Flutterwave's 60-second SLA
  // Process the event asynchronously in the background
  return new Response(JSON.stringify({ status: "success" }), { status: 200 });
}

Test Vectors & Sandbox Testing

测试向量与沙箱测试

RailTest InputExpected Behavior
3DS MastercardCard:
5531881111111111
, CVV:
123
, Expiry:
09/32
Requires OTP -> Enter
12345
-> Successful
Verve Card (PIN)Card:
5060666666666666
, CVV:
111
, Expiry:
05/29
Requires PIN
1234
, then OTP
12345
-> Successful
Decline SimulationCard:
5531882222222222
, CVV:
123
, Expiry:
09/32
Direct decline (
Insufficient funds
)
Kenya M-PesaPhone:
2540700000000
Auto-approves simulated STK push in sandbox
Bank ResolutionAccount:
0690000031
, Bank:
044
(Access Bank)
Resolves name:
FLW TEST ACCOUNT

支付通道测试输入预期行为
3DS Mastercard银行卡:
5531881111111111
,CVV:
123
,有效期:
09/32
需要 OTP -> 输入
12345
-> 成功
Verve Card(PIN)银行卡:
5060666666666666
,CVV:
111
,有效期:
05/29
需要 PIN
1234
,然后输入 OTP
12345
-> 成功
拒绝支付模拟银行卡:
5531882222222222
,CVV:
123
,有效期:
09/32
直接拒绝(
Insufficient funds
)
Kenya M-Pesa手机号:
2540700000000
在沙箱中自动批准模拟的 STK push
银行账户解析账号:
0690000031
,银行:
044
(Access Bank)
解析出姓名:
FLW TEST ACCOUNT

Best Practices for Developers and AI Agents

开发者和 AI Agent 最佳实践

  1. Version Isolation: Do not mix v3 request fields (e.g.
    customer.email
    embedded directly) with v4 structures (
    payment_instruction
    ,
    customer_id
    ).
  2. Never Trust Redirect URLs: Never fulfill an order simply because a customer landed on
    redirect_url?status=successful
    . Always execute server-side verification.
  3. Deterministic References: Provide a unique
    tx_ref
    or
    reference
    on every payment and payout to avoid collision and guarantee idempotency.
  4. Credential Security: Store
    FLW_SECRET_KEY
    ,
    FLW_V4_CLIENT_ID
    ,
    FLW_V4_CLIENT_SECRET
    , and
    FLW_WEBHOOK_HASH
    in secure environment variables. Never commit credentials to git.
  1. 版本隔离: 不要将 v3 请求字段(例如直接嵌入的
    customer.email
    )与 v4 结构(
    payment_instruction
    、
    customer_id
    )混用。
  2. 永不信任重定向 URL: 不要仅因为客户跳转到了
    redirect_url?status=successful
    就履约。始终执行服务端验证。
  3. 确定性引用: 为每笔支付和资金转移提供唯一的
    tx_ref
    或
    reference
    ,避免冲突并确保幂等性。
  4. 凭据安全: 将
    FLW_SECRET_KEY
    、
    FLW_V4_CLIENT_ID
    、
    FLW_V4_CLIENT_SECRET
    和
    FLW_WEBHOOK_HASH
    存储在安全的环境变量中。切勿将凭据提交到 git。