mcpField
F
flutterwave

flutterwave

github.com/devmubarak1Development3150 installsUpdated 2w ago
Repository

About flutterwave

flutterwave is an AI agent skill for developers integrating the Flutterwave API. It covers v3 and v4 in a TypeScript client, with guidance and recipes for payments, payouts, verification, and related operations.

Key features

  1. 01Support for Flutterwave API v3 and v4
  2. 02TypeScript client with v4 token caching
  3. 03Payment and payout operation recipes
  4. 04Webhook verification guidance
  5. 05Sandbox test vectors

Use cases

  1. 01Create hosted checkout payments
  2. 02Verify transactions before fulfillment
  3. 03Initiate transfers and inspect their status
  4. 04Handle webhooks with secret-hash checks

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/devmubarak1/flutterwave --skill flutterwave

After installing, type/flutterwavein Claude Code to confirm it loaded.

Details

From the project README

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.


Architecture & Version Comparison

Featurev3 (Production Stable)v4 (Next-Gen Public Beta)
StatusGlobally deployed, battle-testedPublic Beta (high performance, microservices)
Base URLhttps://api.flutterwave.com/v3Sandbox: https://developersandbox-api.flutterwave.com
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/APOST https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token
Tracing HeaderOptionalX-Trace-Id (12–255 char UUID, required)
IdempotencyCustom tx_ref in payloadX-Idempotency-Key header + reference in payload
Primary InflowPOST /payments or POST /chargesPOST /charges with customer_id and payment_method_id
Primary OutflowPOST /transfers with account detailsPOST /transfers with payment_instruction & recipient_id

Universal Client Implementation (TypeScript)

Zero external CLI dependencies. Provides full dual-version execution, automatic v4 OAuth 2.0 token caching, and request tracing.

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;
  }
}

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 CheckoutPOST/payments/chargestx_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 TransactionsGET/transactions/chargesFilters by from, to, status, currency, and pagination page.
Direct Card ChargePOST/charges?type=card/chargesv3 uses 3DES encrypted payload; v4 uses payment_method_id with authorization.
Validate Charge (OTP)POST/validate-charge/charges/{id}/validateSubmits customer OTP challenge code (flw_ref, otp).
Bank Transfer CollectionPOST/charges?type=bank_transfer/virtual-accountsGenerates dynamic dedicated Nigerian NIP bank account with expiry timer.
Mobile Money (M-Pesa)POST/charges?type=mpesa/chargesTriggers instant STK push prompt on customer mobile device in Kenya (KES).
Mobile Money (Ghana)POST/charges?type=mobile_money_ghana/chargesCharges MTN, Telecel (Vodafone voucher), or AirtelTigo wallets (GHS).
USSD PaymentPOST/charges?type=ussd/chargesReturns bank-specific interactive dial code (e.g. *737*...# for GTBank) for offline payments.
Tokenized ChargePOST/tokenized-charges/charges (recurring: true)Re-bills stored card.token for 1-click checkout and subscriptions without card entry.
Initiate Transfer (Payout)POST/transfers/transfersDisburses funds to bank accounts or mobile wallets. v4 uses payment_instruction + action.
Get Transfer StatusGET/transfers/{id}/transfers/{id}Inspects payout status (SUCCESSFUL, FAILED, PENDING).
Retry TransferPOST/transfers/{id}/retries/transfers/{id}/retryResubmits failed transfer for reprocessing.
List BeneficiariesGET/beneficiaries/transfer-recipientsLists saved bank account and mobile wallet payout contacts.
Create BeneficiaryPOST/beneficiaries/transfer-recipientsSaves recipient (account_number, account_bank, beneficiary_name, currency).
Create Virtual AccountPOST/virtual-account-numbers/virtual-accountsProvisions permanent or dynamic virtual bank accounts for direct customer wallet funding.
Bulk Virtual AccountsPOST/bulk-virtual-account-numbers/virtual-accountsBatches creation of dedicated virtual accounts for fintech platforms.
Create Subaccount (Split)POST/subaccountsSplit configurationConfigures vendor subaccount for marketplace splits (split_type: "percentage" | "flat").
Create Payment PlanPOST/payment-plansSubscription plansSets automated subscription billing intervals (daily, weekly, monthly, yearly).
Create Virtual CardPOST/virtual-cardsCard issuingIssues digital dollar (USD) or local currency debit cards with billing address.
Resolve Bank AccountPOST/accounts/resolve/accounts/lookupVerifies Nigerian bank account number and resolves official registered account holder name.
Validate BVNPOST/bvn/matchKYC validationConfirms Bank Verification Number matches customer legal identity for compliance.
Issue RefundPOST/transactions/{id}/refund/refundsIssues full or partial refunds directly to customer's source account.
Manage ChargebacksGET / PUT/chargebacks/chargebacksQueries open dispute claims and submits defense evidence before the due date.
List SettlementsGET/settlements/settlementReconciles bank deposits, gross transactions, and processor fee deductions.
Pay Bills & AirtimePOST/billsUtility billsPurchases airtime, mobile data bundles, power tokens, and renews cable TV subscriptions.

Action Code Recipes

1. Create a Standard Checkout Payment Link (v3)
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: "[email protected]",
      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)
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)
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

Flutterwave verifies webhooks using a secret hash configured in your Flutterwave Dashboard (Settings > Webhooks) and passed in the verif-hash HTTP header:

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

Best Practices for Developers and AI Agents

  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.

Comments

Markdown supported