The Paystack adapter enables payment processing across African markets with support for cards, bank transfers, and mobile wallets in Nigeria, Ghana, South Africa, and Kenya.
Configuration
Required Credentials
secretKey: Paystack secret key (starts with sk_test_ or sk_live_)
webhookSecret: Optional webhook signing secret (falls back to secretKey)
Basic Setup
import { PaystackAdapter , VaultClient } from '@vaultsaas/core' ;
const vault = new VaultClient ({
providers: {
paystack: {
adapter: PaystackAdapter ,
config: {
secretKey: process . env . PAYSTACK_SECRET_KEY ! ,
webhookSecret: process . env . PAYSTACK_WEBHOOK_SECRET ,
},
},
},
routing: {
rules: [{ match: { default: true }, provider: 'paystack' }],
},
});
Configuration Options
config : {
secretKey : string ; // Required
webhookSecret ?: string ; // Optional, falls back to secretKey
baseUrl ?: string ; // Optional, defaults to https://api.paystack.co
timeoutMs ?: number ; // Request timeout (default: 15000ms)
fetchFn ?: typeof fetch ; // Custom fetch implementation
}
Supported Capabilities
Payment Methods
Card : Credit and debit cards (Visa, Mastercard, Verve)
Bank Transfer : Direct bank transfers
Wallet : Mobile money and digital wallets
Currencies
NGN (Nigerian Naira), GHS (Ghanaian Cedi), ZAR (South African Rand), KES (Kenyan Shilling), USD (US Dollar)
Countries
NG (Nigeria), GH (Ghana), ZA (South Africa), KE (Kenya)
Usage Examples
Charge a Card
Important : Paystack requires customer.email for all charge and authorize requests.
const result = await vault . charge ({
amount: 2500 ,
currency: 'NGN' ,
paymentMethod: {
type: 'card' ,
token: 'AUTH_test_123' , // Authorization code from previous transaction
},
customer: {
email: 'buyer@example.com' , // Required!
},
metadata: {
orderId: 'order_789' ,
},
});
console . log ( result . id ); // Transaction reference
console . log ( result . status ); // 'completed', 'pending', etc.
Bank Transfer Payment
const bankTransfer = await vault . charge ({
amount: 5000 ,
currency: 'NGN' ,
paymentMethod: {
type: 'bank_transfer' ,
bankCode: '058' , // Bank code
accountNumber: '0123456789' ,
},
customer: {
email: 'buyer@example.com' ,
},
});
Mobile Wallet Payment
const walletPayment = await vault . charge ({
amount: 1000 ,
currency: 'GHS' ,
paymentMethod: {
type: 'wallet' ,
walletType: 'mobile_money' ,
token: 'wallet_token_123' ,
},
customer: {
email: 'buyer@example.com' ,
},
});
Authorize & Capture Flow
Paystack’s capture flow requires retrieving the authorization code and customer email from the verify response.
// Step 1: Authorize
const auth = await vault . authorize ({
amount: 10000 ,
currency: 'NGN' ,
paymentMethod: {
type: 'card' ,
token: 'AUTH_code_123' ,
},
customer: {
email: 'buyer@example.com' , // Required
},
});
console . log ( auth . status ); // Should be 'authorized' or pending
// Step 2: Capture (uses stored authorization)
const captured = await vault . capture ({
transactionId: auth . id ,
amount: 10000 , // Optional: partial capture
});
console . log ( captured . status ); // 'completed'
Refund a Payment
const refund = await vault . refund ({
transactionId: 'txn_ref_123' ,
amount: 1000 , // Amount in kobo/pesewas/cents
});
console . log ( refund . status ); // 'completed', 'pending', or 'failed'
Void (Full Refund)
// Void is implemented as a full refund in Paystack
const voided = await vault . void ({
transactionId: 'txn_ref_123' ,
});
console . log ( voided . status );
Webhooks
Signature Verification
Paystack webhooks use HMAC-SHA512 signature verification.
Critical : Pass the raw request body as a Buffer. Parsed JSON will fail signature verification.
Webhook Handler
import express from 'express' ;
const app = express ();
app . post ( '/webhooks/paystack' ,
express . raw ({ type: 'application/json' }),
async ( req , res ) => {
try {
const event = await vault . handleWebhook (
'paystack' ,
req . body , // Raw Buffer
req . headers as Record < string , string >
);
console . log ( 'Event type:' , event . type );
console . log ( 'Transaction ID:' , event . transactionId );
switch ( event . type ) {
case 'payment.completed' :
// Handle successful charge
break ;
case 'payment.failed' :
// Handle failed payment
break ;
case 'payment.refunded' :
// Handle refund
break ;
case 'payment.disputed' :
// Handle chargeback
break ;
}
res . json ({ received: true });
} catch ( error ) {
console . error ( 'Webhook verification failed:' , error );
res . status ( 400 ). send ( 'Verification failed' );
}
}
);
Paystack includes the signature in the x-paystack-signature header.
Supported Event Types
Paystack Event VaultSaaS Event Type charge.successpayment.completedcharge.failedpayment.failedcharge.pendingpayment.pendingrefund.processed / refund.successpayment.refundedrefund.pendingpayment.partially_refundeddispute.create / charge.dispute.createpayment.disputeddispute.resolve / charge.dispute.resolvepayment.dispute_resolvedtransfer.successpayout.completedtransfer.failedpayout.failed
Common Pitfalls
Missing Customer Email customer.email is required for all charge and authorize requests. Requests without email will fail.
// ❌ Will fail
await vault . charge ({
amount: 1000 ,
currency: 'NGN' ,
paymentMethod: { type: 'card' , token: 'AUTH_123' },
});
// ✅ Correct
await vault . charge ({
amount: 1000 ,
currency: 'NGN' ,
paymentMethod: { type: 'card' , token: 'AUTH_123' },
customer: { email: 'buyer@example.com' },
});
Capture Requirements Capture requires a previously stored authorization code and customer email from the verify response. Test the full flow.
Missing or malformed x-paystack-signature headers will fail webhook handling. Ensure your webhook endpoint is configured correctly in Paystack dashboard.
Test Mode
Test Credentials
Use test secret keys for staging:
config : {
secretKey : 'sk_test_your_test_key_here' ,
webhookSecret : process . env . PAYSTACK_TEST_WEBHOOK_SECRET ,
}
Testing Capture Flow
When testing capture, validate both the verify step (to get authorization code) and the charge_authorization step:
Create test authorization
Verify transaction to get authorization_code
Use authorization_code in capture request
Test Events
Use Paystack’s webhook testing tools in the dashboard to send test events to your endpoint.
Payment Status Mapping
Paystack Status VaultSaaS Status successcompletedpending / ongoing / queuedpendingabandonedcancelledfailed / reversedfailedOthers failed
Error Handling
try {
const result = await vault . charge ({ /* ... */ });
} catch ( error ) {
if ( error . code === 'INVALID_REQUEST' ) {
console . error ( 'Request validation failed:' , error . message );
} else if ( error . code === 'PROVIDER_ERROR' ) {
console . error ( 'Provider error:' , error . hint . providerMessage );
console . error ( 'Provider code:' , error . hint . providerCode );
console . error ( 'HTTP status:' , error . hint . httpStatus );
}
}
Reference
Next Steps
Multi-Provider Setup Combine multiple providers with routing
Event Handling Process payment lifecycle events