Install
$ agentstack add skill-babatunde-fatai-ai-agent-skills-library-flutterwave-integration ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
For Coding Agents: Start Here
Before reading any examples or framework guides, you MUST read:
[Agent Execution Spec](references/AGENTEXECUTIONSPEC.md)
This file defines the payment safety contract and execution order that must be followed to prevent fraud and incorrect implementations. Start by identifying what you are trying to implement:
- If implementing frontend popup/redirect → read [references/one-time-payments.md](references/one-time-payments.md)
- If implementing backend verification → read [references/express-implementation.md](references/express-implementation.md) or [references/nextjs-implementation.md](references/nextjs-implementation.md)
- If implementing webhooks → read [references/webhooks.md](references/webhooks.md)
- If implementing subscriptions → read [references/subscriptions.md](references/subscriptions.md)
- If implementing mobile money (M-Pesa, MTN, Airtel) → read [references/mobile-money.md](references/mobile-money.md)
- If implementing bank transfers/payouts → read [references/bank-transfers.md](references/bank-transfers.md)
- If unsure about responsibilities or safety → read [references/AGENTEXECUTIONSPEC.md](references/AGENTEXECUTIONSPEC.md) first
- If debugging issues → read [references/troubleshooting.md](references/troubleshooting.md)
- If you need v3 vs v4 migration or enckey handling → read [references/flutterwave-specific.md](references/flutterwave-specific.md)
If you are unsure about responsibilities, safety, or execution order, read: references/AGENTEXECUTIONSPEC.md first.
Condensed Payment Safety Rules
- Generate unique
tx_refFIRST, store in DB with status=pending, THEN call Flutterwave. - Always convert and store amounts in smallest currency unit (kobo/pesewas/cents).
- Never trust client-side success callbacks; always verify on backend.
- Webhook handlers must verify signature (
verif-hashheader OR HMAC-SHA256) before processing. - Verify that the verified amount exactly matches the expected DB amount before fulfilling.
- Handle
success-pending-validationstatus (wait for webhook confirmation). - Ensure idempotency: if order.status == 'paid', exit immediately.
Table of Contents (all contained in ./references/)
- [Agent Execution Spec](references/AGENTEXECUTIONSPEC.md) - READ FIRST
- [Quick Reference](#quick-reference)
- [Payment Flow Overview](#payment-flow-overview)
- [Core Implementation](#core-implementation)
- [Webhook Essentials](#webhook-essentials)
- [Framework Guides](#framework-guides)
- [Deployment Checklist](#deployment-checklist)
- [Quick Troubleshooting](#quick-troubleshooting)
- [Database example](references/database-example.md)
- [Local webhook testing with ngrok](references/testing-with-ngrok.md)
Quick Reference
Environment Variables
# Backend (NEVER expose these)
FLW_SECRET_KEY=FLWSECK_TEST-xxxx # or FLWSECK-xxxx for production
FLW_SECRET_HASH=your_webhook_hash # Set in Dashboard → Settings → Webhooks
# Frontend (safe to expose)
FLW_PUBLIC_KEY=FLWPUBK_TEST-xxxx # or FLWPUBK-xxxx for production
# Optional
FLW_ENCRYPTION_KEY=FLWSECK_TESTxxxx # For direct card charges only
API Configuration
const FLW_BASE_URL = 'https://api.flutterwave.com/v3';
const headers = {
Authorization: `Bearer ${process.env.FLW_SECRET_KEY}`,
'Content-Type': 'application/json',
};
tx_ref Generation (CRITICAL)
import crypto from 'crypto';
// ALWAYS generate tx_ref BEFORE calling Flutterwave
function generateTxRef(prefix = 'FLW'): string {
const timestamp = Date.now().toString(36);
const random = crypto.randomBytes(4).toString('hex');
return `${prefix}_${timestamp}_${random}`.toUpperCase();
}
// Example: FLW_LK5J2M8_A1B2C3D4
Flow:
- Generate unique
tx_ref - Store in database with status=pending
- Call Flutterwave
/v3/paymentswith thistx_ref - Redirect user to returned
link - Webhook events will contain this same
tx_ref
Unlike Paystack, YOU generate the reference before calling the API.
Currency Units (CRITICAL)
| Currency | Country | Smallest Unit | Multiplier | |----------|---------|---------------|------------| | NGN | Nigeria | kobo | 100 | | GHS | Ghana | pesewas | 100 | | KES | Kenya | cents | 100 | | ZAR | South Africa | cents | 100 | | UGX | Uganda | cents | 100 | | XOF | Francophone | francs | 100 | | XAF | Central Africa | francs | 100 | | USD | International | cents | 100 |
function toSmallestUnit(amount: number): number {
return Math.round(amount * 100);
}
Supported Payment Channels
type FlutterwaveChannel =
| 'card'
| 'banktransfer'
| 'ussd'
| 'credit'
| 'mobilemoneyghana'
| 'mobilemoneyuganda'
| 'mobilemoneyrwanda'
| 'mobilemoneyzambia'
| 'mobilemoneytanzania'
| 'mobilemoneyfranco'
| 'mpesa'
| 'barter';
Payment Flow Overview
One-Time Payment Flow
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │────▶│ Backend │────▶│ Flutterwave │────▶│ Backend │
│ (initiate) │ │ (initialize)│ │ (payment) │ │ (verify) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ 1. Request │ 2. POST │ 3. Pay via │ 4. GET
│ payment │ /v3/payments │ hosted page │ /v3/transactions
│ │ │ │ /:id/verify
│ │ Returns: │ Returns: │
│ │ link (hosted) │ tx_ref, flw_ref │ Returns:
│ │ │ transaction_id │ status: successful
Decision: Hosted vs Inline
| Method | Use When | Pros | Cons | |--------|----------|------|------| | Hosted (Redirect) | Simple integration, server-rendered | No JS required, PCI compliant | User leaves your site | | Inline (Popup) | SPA, better UX | User stays on site | Requires Inline.js SDK |
Core Implementation
TypeScript Interfaces
interface FlutterwaveResponse {
status: 'success' | 'error';
message: string;
data: T;
}
interface InitializePaymentData {
link: string; // Hosted payment page URL
}
interface VerifyTransactionData {
id: number;
tx_ref: string;
flw_ref: string;
status: 'successful' | 'pending' | 'failed' | 'success-pending-validation';
amount: number;
currency: string;
charged_amount: number;
app_fee: number;
customer: {
id: number;
email: string;
name: string;
phone_number: string;
};
card?: {
first_6digits: string;
last_4digits: string;
type: string;
expiry: string;
};
meta?: Record;
created_at: string;
}
interface WebhookEvent {
event: string;
data: VerifyTransactionData;
}
Initialize Transaction
interface InitializeParams {
tx_ref: string; // YOUR unique reference (required)
amount: number; // In smallest unit
currency: string; // NGN, GHS, KES, etc.
redirect_url: string; // Where to redirect after payment
customer: {
email: string;
name?: string;
phonenumber?: string;
};
customizations?: {
title?: string;
description?: string;
logo?: string;
};
payment_options?: string; // Comma-separated: "card,banktransfer,ussd"
meta?: Record;
}
async function initializePayment(params: InitializeParams): Promise {
const response = await fetch(`${FLW_BASE_URL}/payments`, {
method: 'POST',
headers,
body: JSON.stringify(params),
});
const result: FlutterwaveResponse = await response.json();
if (result.status !== 'success') {
throw new Error(result.message);
}
return result.data.link; // Redirect URL
}
Verify Transaction (CRITICAL)
async function verifyTransaction(
transactionId: number,
expectedAmount: number,
expectedCurrency: string
): Promise {
const response = await fetch(
`${FLW_BASE_URL}/transactions/${transactionId}/verify`,
{ headers }
);
const result: FlutterwaveResponse = await response.json();
if (result.status !== 'success') {
throw new Error(result.message);
}
// CRITICAL: Verify payment status
if (result.data.status !== 'successful') {
throw new Error(`Payment not successful: ${result.data.status}`);
}
// CRITICAL: Verify amount matches expected
if (result.data.amount !== expectedAmount) {
throw new Error(`Amount mismatch: expected ${expectedAmount}, got ${result.data.amount}`);
}
// CRITICAL: Verify currency matches
if (result.data.currency !== expectedCurrency) {
throw new Error(`Currency mismatch: expected ${expectedCurrency}, got ${result.data.currency}`);
}
return result.data;
}
Webhook Essentials
Webhooks notify your server of payment events. Signature verification is mandatory for security.
Webhook Verification (Two Methods)
Method 1: Simple verif-hash (Recommended)
function handleWebhook(req: Request): Response {
const signature = req.headers.get('verif-hash');
const secretHash = process.env.FLW_SECRET_HASH;
if (!signature || signature !== secretHash) {
return new Response('Invalid signature', { status: 401 });
}
const event: WebhookEvent = await req.json();
await processWebhookEvent(event);
return new Response('OK', { status: 200 });
}
Method 2: HMAC-SHA256 (More Secure)
import crypto from 'crypto';
function verifyFlutterwaveSignature(
payload: string,
signature: string,
secretHash: string
): boolean {
const hash = crypto
.createHmac('sha256', secretHash)
.update(payload)
.digest('base64');
// Use timing-safe comparison
try {
return crypto.timingSafeEqual(
Buffer.from(hash),
Buffer.from(signature)
);
} catch {
return false;
}
}
Key Webhook Events
| Event | When | Action | |-------|------|--------| | charge.completed | Payment completed | Fulfill order, update database | | charge.failed | Payment failed | Notify user, log for analysis | | transfer.completed | Payout completed | Update transfer status | | transfer.failed | Payout failed | Retry or notify admin | | subscription.cancelled | Subscription cancelled | Revoke access |
Idempotency
async function processWebhookEvent(event: WebhookEvent): Promise {
const txRef = event.data.tx_ref;
// Check if already processed
const order = await db.orders.findUnique({ where: { txRef } });
if (!order) {
console.log(`Order not found for tx_ref: ${txRef}`);
return;
}
if (order.status === 'paid') {
console.log(`Order ${txRef} already paid, skipping`);
return;
}
// Process based on event type
if (event.event === 'charge.completed' && event.data.status === 'successful') {
await fulfillOrder(order, event.data);
}
}
See [references/webhooks.md](references/webhooks.md) for complete event handling patterns.
Framework Guides
Next.js (App Router)
Recommended file structure:
app/api/flutterwave/
├── initialize/route.ts # POST - Initialize payment
├── verify/route.ts # GET - Verify payment
└── webhook/route.ts # POST - Handle webhooks
components/
└── FlutterwaveButton.tsx # Client component
lib/
└── flutterwave.ts # Utility functions
See [references/nextjs-implementation.md](references/nextjs-implementation.md) for complete implementation.
Express.js
Recommended structure:
routes/
└── flutterwave.routes.ts
controllers/
└── flutterwave.controller.ts
middleware/
└── flutterwave.middleware.ts # Signature verification
services/
└── flutterwave.service.ts # API calls
See [references/express-implementation.md](references/express-implementation.md) for complete implementation.
Deployment Checklist
Before Going Live
- [ ] Environment Variables
- [ ]
FLW_SECRET_KEYset (FLWSECK-xxxx for production) - [ ]
FLW_PUBLIC_KEYset (FLWPUBK-xxxx for production) - [ ]
FLW_SECRET_HASHset (from Dashboard) - [ ] Keys not committed to version control
- [ ] Webhook Configuration
- [ ] Webhook URL configured in Flutterwave Dashboard
- [ ] HTTPS endpoint (required for production)
- [ ] Signature verification implemented
- [ ] Return 200 OK within 30 seconds
- [ ] Payment Verification
- [ ] Amount verification implemented
- [ ] Currency verification implemented
- [ ] Status check implemented (only process
successful) - [ ] Handle
success-pending-validationstatus - [ ] Idempotency for webhook handling
- [ ] Testing
- [ ] Test with Flutterwave test cards
- [ ] Test mobile money flows
- [ ] Test webhook with ngrok (local)
- [ ] Test failed payment scenarios
Test Cards
| Card Number | CVV | Expiry | PIN | OTP | Result | |-------------|-----|--------|-----|-----|--------| | 5531886652142950 | 564 | 09/32 | 3310 | 12345 | Success | | 5258585922666506 | 883 | 09/31 | 3310 | 12345 | Insufficient Funds | | 5399838383838381 | 470 | 10/31 | 3310 | 12345 | Declined |
Test Mobile Money
- Phone:
0551234987(Ghana) - OTP:
123456
Quick Troubleshooting
Common Errors
| Error | Cause | Solution | |-------|-------|----------| | Invalid API key | Wrong or missing key | Check FLWSECRETKEY env var | | Invalid tx_ref | Duplicate or malformed | Generate unique txref per transaction | | Amount mismatch | Amount verification failed | Store and compare in smallest unit | | Invalid signature | Webhook verification failed | Check FLWSECRET_HASH matches dashboard | | Transaction not found | Wrong transaction ID | Use correct id from callback/webhook |
Debug Checklist
- API calls failing?
- Check Authorization header format:
Bearer FLWSECK-xxx - Verify base URL:
https://api.flutterwave.com/v3 - Check test vs live keys match environment
- Webhook not received?
- Verify URL is publicly accessible (HTTPS)
- Check Flutterwave Dashboard for delivery logs
- Ensure you return 200 OK quickly
- Status issues?
- Handle
success-pending-validationstatus - Poll for status or wait for webhook
See [references/troubleshooting.md](references/troubleshooting.md) for comprehensive debugging guide.
Official Documentation
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Babatunde-Fatai
- Source: Babatunde-Fatai/ai-agent-skills-library
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.