skills/paystack-charges/SKILL.md
Paystack Charge API — initiate direct charges via card, bank account, USSD, mobile money, QR, EFT, Capitec Pay, and bank transfer channels. Handle multi-step authentication with Submit PIN, Submit OTP, Submit Phone, Submit Birthday, and Submit Address endpoints. Check pending charge status. Use this skill whenever building a custom payment flow that needs direct channel control instead of the standard checkout, implementing bank debit or USSD payments, handling card PIN/OTP verification flows, processing mobile money payments in Ghana/Kenya, or checking the status of a pending charge. Also use when you see references to /charge, submit_pin, submit_otp, or send_birthday in Paystack code.
npx skillsauth add rexedge/paystack paystack-chargesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The Charge API gives you direct control over which payment channel to use, bypassing the standard Paystack checkout. It supports card, bank, USSD, mobile money, QR, EFT, Capitec Pay, and bank transfers.
Depends on: paystack-setup for the
paystackRequesthelper and environment config.
The Charge API follows a multi-step flow. After creating a charge, Paystack may request additional authentication:
Create Charge → Check status → If "send_pin" → Submit PIN → Check status
→ If "send_otp" → Submit OTP → Check status
→ If "send_phone" → Submit Phone → Check status
→ If "send_birthday"→ Submit Birthday→ Check status
→ If "send_address"→ Submit Address → Check status
→ If "pending" → Wait 10s → Check Pending
→ If "success" → Done ✓
| Method | Endpoint | Description |
| --- | --- | --- |
| POST | /charge | Create a charge |
| POST | /charge/submit_pin | Submit PIN for verification |
| POST | /charge/submit_otp | Submit OTP for verification |
| POST | /charge/submit_phone | Submit phone number |
| POST | /charge/submit_birthday | Submit birthday |
| POST | /charge/submit_address | Submit address (AVS) |
| GET | /charge/:reference | Check pending charge status |
POST /charge
| Param | Type | Required | Description |
| --- | --- | --- | --- |
| email | string | Yes | Customer's email address |
| amount | string | Yes | Amount in subunits |
| reference | string | No | Unique transaction reference |
| metadata | object | No | Custom data for post-payment processing |
Send ONE of these objects to select the payment channel:
const result = await paystackRequest("/charge", {
method: "POST",
body: JSON.stringify({
email: "[email protected]",
amount: "10000",
bank: {
code: "057", // Bank code (get from List Banks endpoint)
account_number: "0000000000",
},
birthday: "1995-12-23", // Required for some banks
}),
});
{
email: "[email protected]",
amount: "10000",
bank: {
code: "50211",
account_number: "0000000000",
phone: "08012345678", // Required for Kuda
token: "123456", // 6-digit code from Kuda app
}
}
{
email: "[email protected]",
amount: "10000",
ussd: { type: "737" } // 737 (GTBank) — currently only supported type
}
// MTN Ghana
{
email: "[email protected]",
amount: "10000",
mobile_money: {
phone: "0551234987",
provider: "mtn" // mtn | atl | vod (Ghana), mpesa (Kenya), orange | wave (CIV)
}
}
// M-Pesa Kenya (till number)
{
email: "[email protected]",
amount: "10000",
mobile_money: {
account: "TILL_NUMBER", // Use account instead of phone for M-Pesa till
provider: "mptill" // or "mpesa" for phone-based, "mpesa_offline"
}
}
{
email: "[email protected]",
amount: "10000",
qr: { provider: "scan-to-pay" }
}
{
email: "[email protected]",
amount: "10000",
eft: { provider: "ozow" }
}
{
email: "[email protected]",
amount: "10000",
capitec_pay: {
identifier_key: "CELLPHONE", // CELLPHONE | IDNUMBER | ACCOUNTNUMBER
identifier_value: "0712345678"
}
}
{
email: "[email protected]",
amount: "10000",
bank_transfer: {
account_expires_at: "2024-12-31T23:59:59.000Z" // Expiry for generated account
}
}
{
email: "[email protected]",
amount: "10000",
authorization_code: "AUTH_72btv547", // From a previous successful charge
pin: "1234" // Only for non-reusable auth codes
}
| Param | Type | Description |
| --- | --- | --- |
| split_code | string | Split code e.g. SPL_98WF13Eb3w |
| subaccount | string | Subaccount code e.g. ACCT_8f4s1eq7ml6rlzj |
| transaction_charge | integer | Override split amount (subunits) |
| bearer | string | "account" or "subaccount" — who pays fees |
POST /charge/submit_pin
When data.status === "send_pin" in the charge response:
const result = await paystackRequest("/charge/submit_pin", {
method: "POST",
body: JSON.stringify({
pin: "1234",
reference: "5bwib5v6anhe9xa",
}),
});
POST /charge/submit_otp
When data.status === "send_otp":
const result = await paystackRequest("/charge/submit_otp", {
method: "POST",
body: JSON.stringify({
otp: "123456",
reference: "5bwib5v6anhe9xa",
}),
});
POST /charge/submit_phone
When data.status === "send_phone":
const result = await paystackRequest("/charge/submit_phone", {
method: "POST",
body: JSON.stringify({
phone: "08012345678",
reference: "5bwib5v6anhe9xa",
}),
});
POST /charge/submit_birthday
When data.status === "send_birthday":
const result = await paystackRequest("/charge/submit_birthday", {
method: "POST",
body: JSON.stringify({
birthday: "1995-12-23",
reference: "5bwib5v6anhe9xa",
}),
});
POST /charge/submit_address
When data.status === "send_address" (AVS — Address Verification System):
const result = await paystackRequest("/charge/submit_address", {
method: "POST",
body: JSON.stringify({
reference: "7c7rpkqpc0tijs8",
address: "140 N 2ND ST",
city: "Stroudsburg",
state: "PA",
zipcode: "18360",
}),
});
GET /charge/:reference
When status is "pending", wait at least 10 seconds before checking:
const result = await paystackRequest<{
status: string;
reference: string;
amount: number;
}>(`/charge/${encodeURIComponent(reference)}`);
// result.data.status: "success" | "failed" | "pending"
async function processDirectCharge(chargeData: any) {
let result = await paystackRequest<any>("/charge", {
method: "POST",
body: JSON.stringify(chargeData),
});
// Handle multi-step authentication
while (result.data.status !== "success" && result.data.status !== "failed") {
switch (result.data.status) {
case "send_pin":
const pin = await promptUserForPIN();
result = await paystackRequest("/charge/submit_pin", {
method: "POST",
body: JSON.stringify({ pin, reference: result.data.reference }),
});
break;
case "send_otp":
const otp = await promptUserForOTP();
result = await paystackRequest("/charge/submit_otp", {
method: "POST",
body: JSON.stringify({ otp, reference: result.data.reference }),
});
break;
case "send_phone":
const phone = await promptUserForPhone();
result = await paystackRequest("/charge/submit_phone", {
method: "POST",
body: JSON.stringify({ phone, reference: result.data.reference }),
});
break;
case "send_birthday":
const birthday = await promptUserForBirthday();
result = await paystackRequest("/charge/submit_birthday", {
method: "POST",
body: JSON.stringify({ birthday, reference: result.data.reference }),
});
break;
case "send_address":
const address = await promptUserForAddress();
result = await paystackRequest("/charge/submit_address", {
method: "POST",
body: JSON.stringify({ ...address, reference: result.data.reference }),
});
break;
case "pending":
await new Promise((r) => setTimeout(r, 10000)); // Wait 10 seconds
result = await paystackRequest(`/charge/${result.data.reference}`);
break;
default:
throw new Error(`Unknown charge status: ${result.data.status}`);
}
}
return result.data;
}
birthday as part of the initial requestpending status, wait at least 10 seconds before polling to avoid rate limitingmtn, atl, vod for Ghana; mpesa for Kenya; orange, wave for Côte d'Ivoire737 (GTBank) in Nigeriadevelopment
Paystack webhook integration — signature validation with HMAC SHA512, event parsing, IP whitelisting, retry policy, and all supported event types. Use this skill whenever setting up a webhook endpoint for Paystack, validating x-paystack-signature headers, handling charge.success or transfer.success events, debugging webhook delivery failures, implementing idempotent event processing, or building any server-side Paystack event listener. Also use when encountering webhook timeout issues or needing the list of Paystack webhook IP addresses.
tools
Paystack Verification API — KYC verification tools for resolving bank accounts, validating account ownership, and looking up card BIN information. Use this skill whenever verifying bank account details before transfers, confirming account holder names, validating customer identity for compliance, looking up card brand/type/bank from BIN, or implementing KYC flows. Also use when you see references to /bank/resolve, /bank/validate, /decision/bin endpoints, or need to match account numbers to names.
development
Paystack Transfers API — send money to bank accounts and mobile wallets. Initiate single and bulk transfers, finalize OTP-verified transfers, list, fetch, and verify transfer status. Use this skill whenever implementing payouts, disbursements, vendor payments, withdrawal flows, or any feature that sends money from your Paystack balance to recipients. Also use when you see references to transfer_code, TRF_ prefixed codes, the /transfer endpoint, or need to handle transfer OTP verification.
development
Paystack Transfer Recipients API — create, list, fetch, update, and delete transfer recipients (beneficiaries) for payouts. Supports NUBAN (Nigeria), GHIPSS (Ghana), Mobile Money, BASA (South Africa), and authorization-based recipients. Use this skill whenever adding bank accounts or mobile wallets as payout destinations, creating transfer recipients before initiating transfers, managing beneficiary lists, or doing bulk recipient creation. Also use when you see references to recipient_code, RCP_ prefixed codes, or the /transferrecipient endpoint.