Integrate with the Squad payment gateway API (by Habaripay) for Nigerian payment transactions and payouts. Use this skill whenever the user wants to initialize transactions, process payments, create virtual accounts, handle payouts, manage transfers, or work with Squad's payment infrastructure. Also trigger when the user mentions 'Squad', 'Habaripay', 'Squad payments', 'payment gateway Nigeria', 'virtual account creation', 'direct debit', or needs embedded payment solutions.
Squad (by Habaripay) is a comprehensive payment gateway providing production-ready APIs for payment transaction initialization, virtual account creation, fund transfers, and direct debit capabilities. It enables developers to embed payment processing and payout functionality directly into their applications with reliable, well-documented APIs designed for the Nigerian financial ecosystem.
You're building a payment system, marketplace, subscription platform, or business tool that needs to process payments and send payouts in Nigeria. Squad handles:
Perfect for e-commerce platforms, marketplaces, subscription services, seller payouts, employee payments, and enterprise payment systems operating in Nigeria.
Squad uses Bearer Token authentication. All API requests require an authorization header with your API secret key.
Authorization: Bearer your_secret_key
Key Management:
sandbox_sk_ prefixpk_ prefixSQUAD_API_KEY, SQUAD_SECRET_KEY)| Environment | Base URL |
|---|---|
| Sandbox | https://sandbox-api-d.squadco.com |
| Production | https://api-d.squadco.com |
curl -X POST https://sandbox-api-d.squadco.com/transaction/initiate \
-H "Authorization: Bearer sandbox_sk_YOUR_SANDBOX_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"amount": 50000, "email": "[email protected]"}'
Initialize a payment transaction and receive a checkout link for customers to complete payment.
Endpoint: POST /transaction/initiate
Request Body:
{
"amount": 50000,
"email": "[email protected]",
"currency": "NGN",
"initiate_type": "inline",
"transaction_ref": "TXN-2025-001",
"customer_name": "Amina Okafor",
"customer_phone": "+234901234567",
"payment_channels": ["card", "bank", "ussd"],
"meta_data": {
"order_id": "ORD-12345",
"product": "Digital Service",
"customer_tier": "premium"
},
"pass_charge": false
}
Field Definitions:
amount (required, integer): Transaction amount in kobo (₦500 = 50000 kobo). Kobo is the smallest unit of Nigerian Naira.email (required, string): Customer email address for payment receipt and communicationcurrency (required, string): Presently only supports "NGN" (Nigerian Naira)initiate_type (required, string): Payment initiation method; currently only "inline" is supportedtransaction_ref (required, string): Unique identifier for this transaction. Must be unique across all your transactions. Used for idempotency—duplicate refs within 24 hours return cached result.customer_name (optional, string): Full name of the customercustomer_phone (optional, string): Customer phone number in international formatpayment_channels (optional, array): Array of payment methods to offer. Options: ["card", "bank", "ussd", "transfer"]. If omitted, all channels are available.meta_data (optional, object): Custom key-value pairs for additional transaction context. Returned in webhook and verification responses.pass_charge (optional, boolean): If true, customer bears the transaction charge; if false (default), you bear the chargeResponse:
{
"status": 200,
"success": true,
"data": {
"transaction_id": "TXN_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"transaction_ref": "TXN-2025-001",
"checkout_url": "https://checkout.squad.ng/94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"amount": 50000,
"currency": "NGN",
"status": "pending",
"created_at": "2025-02-24T10:30:00Z"
}
}
Response Field Details:
transaction_id: Squad's unique identifier for the transactioncheckout_url: Share this URL with the customer. When visited, displays the Squad payment modal.status: Initial status is always "pending" until payment is completedcreated_at: ISO 8601 timestamp of transaction creationImportant Notes:
checkout_url with your customer via email, SMS, or by embedding in your applicationCheck the status and details of a transaction using its reference.
Endpoint: GET /transaction/verify/{reference}
Path Parameters:
reference (required, string): The transaction reference provided during initiation (e.g., TXN-2025-001)Response (Successful Payment):
{
"status": 200,
"success": true,
"data": {
"transaction_id": "TXN_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"transaction_ref": "TXN-2025-001",
"amount": 50000,
"currency": "NGN",
"transaction_status": "processed",
"payment_method": "card",
"payment_type": "card payment",
"customer_email": "[email protected]",
"customer_name": "Amina Okafor",
"paid_at": "2025-02-24T10:35:00Z",
"merchant_amount": 48500,
"charge_amount": 1500,
"meta_data": {
"order_id": "ORD-12345"
}
}
}
Response (Pending Payment):
{
"status": 200,
"success": true,
"data": {
"transaction_ref": "TXN-2025-001",
"transaction_status": "pending",
"amount": 50000,
"currency": "NGN"
}
}
Transaction Status Values:
pending: Payment not yet completed; awaiting customer actionprocessed: Payment successfully completed; funds receivedfailed: Payment attempt failed; customer authorization declined or timeout occurredKey Fields:
merchant_amount: Amount you receive (after Squad's processing charge)charge_amount: Squad's transaction feepayment_method: Method used (card, bank_transfer, ussd, transfer)paid_at: Exact timestamp when payment clearedImportant Notes:
processed status with transaction_status === "processed" indicates successful paymentGenerate a dedicated GTBank virtual account number for a customer to receive direct bank transfers. Funds deposited to this account appear in your Squad merchant balance.
Endpoint: POST /virtual-account
Request Body (Individual Account):
{
"customer_name": "Amina Okafor",
"email": "[email protected]",
"phone": "+234901234567",
"meta_data": {
"customer_id": "CUST-001",
"tier": "premium",
"kyc_verified": true
}
}
Request Body (Business Account):
{
"customer_name": "ABC Services Ltd",
"email": "[email protected]",
"phone": "+234901234567",
"bvn": "12345678901",
"business_type": "Limited Liability Company",
"meta_data": {
"business_id": "BIZ-001",
"industry": "Technology"
}
}
Field Definitions:
customer_name (required, string): Name of individual or businessemail (required, string): Valid email address for account notificationsphone (optional, string): Contact phone numberbvn (optional, string): BVN (Bank Verification Number) for business accounts; required for KYC compliancebusiness_type (optional, string): Type of business (Limited Liability Company, Sole Proprietorship, Partnership, etc.)meta_data (optional, object): Custom fields for your referenceResponse:
{
"status": 200,
"success": true,
"data": {
"virtual_account_id": "VA_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"account_number": "1234567890",
"bank_name": "Guaranty Trust Bank",
"bank_code": "090288",
"customer_name": "Amina Okafor",
"customer_email": "[email protected]",
"account_status": "active",
"created_at": "2025-02-24T10:45:00Z",
"is_nuban": true
}
}
Response Fields:
virtual_account_id: Squad's unique identifier for this virtual accountaccount_number: 10-digit NUBAN account number to share with customers for transfersbank_code: GTBank's CBN code (090288)account_status: active indicates the account is ready to receive fundsis_nuban: true indicates this is a standard NUBAN (Nigerian Uniform Bank Account Number)Settlement Details:
Important Notes:
Guaranty Trust Bank - 090288) with customersSend money from your Squad balance to any Nigerian bank account. Squad verifies account details before processing.
Endpoint: POST /payout/transfer
Request Body:
{
"amount": 100000,
"currency": "NGN",
"bank_code": "058",
"account_number": "0123456789",
"account_name": "Chinedu Adeyemi",
"narration": "Payout for services rendered",
"reference": "PAYOUT-2025-001",
"remark": "Monthly commission payout"
}
Field Definitions:
amount (required, integer): Payout amount in kobo (₦1,000 = 100000 kobo)currency (required, string): Must be "NGN" (Nigerian Naira)bank_code (required, string): 3-digit Nigerian bank code (e.g., "058" for GTBank, "033" for UBA, "044" for Access Bank)account_number (required, string): 10-digit NUBAN account number (must be validated using account lookup before payout)account_name (required, string): Beneficiary account name (as returned by account lookup)narration (optional, string): Description visible to the beneficiary in their bank statementreference (required, string): Your unique reference for this payout (for idempotency and tracking)remark (optional, string): Internal note for your recordsNigerian Bank Codes (Common):
| Bank Name | Code |
|---|---|
| Guaranty Trust Bank (GTBank) | 058 |
| United Bank for Africa (UBA) | 033 |
| Zenith Bank | 057 |
| First Bank of Nigeria | 011 |
| Access Bank | 044 |
| FCMB | 214 |
| Ecobank | 050 |
| Stanbic IBTC | 221 |
For a complete list, refer to the CBN Bank Codes documentation.
Response (Initiated):
{
"status": 200,
"success": true,
"data": {
"transaction_id": "TXN_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"reference": "PAYOUT-2025-001",
"amount": 100000,
"currency": "NGN",
"bank_code": "058",
"account_number": "0123456789",
"account_name": "Chinedu Adeyemi",
"status": "initiated",
"initiated_at": "2025-02-24T11:00:00Z",
"narration": "Payout for services rendered"
}
}
Payout Status Values:
initiated: Payout request accepted and queued for processingprocessing: Transfer is being processed by the banking systemsuccess: Funds successfully transferred to beneficiary accountfailed: Transfer failed (insufficient balance, invalid account, bank error, etc.)reversed: Transfer was reversed (unusual; contact Squad support if this occurs)Important Notes:
Retrieve the current status of a payout transfer using its reference.
Endpoint: GET /payout/transfer/status/{reference}
Path Parameters:
reference (required, string): The payout reference provided during transfer initiation (e.g., PAYOUT-2025-001)Response (Successful):
{
"status": 200,
"success": true,
"data": {
"reference": "PAYOUT-2025-001",
"transaction_id": "TXN_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"amount": 100000,
"currency": "NGN",
"status": "success",
"bank_code": "058",
"account_number": "0123456789",
"account_name": "Chinedu Adeyemi",
"recipient_bank": "Guaranty Trust Bank",
"completed_at": "2025-02-24T11:02:30Z"
}
}
Response (Failed):
{
"status": 200,
"success": true,
"data": {
"reference": "PAYOUT-2025-001",
"status": "failed",
"failure_reason": "Invalid account number",
"failed_at": "2025-02-24T11:01:00Z"
}
}
Failure Reasons (Common):
Invalid account number: Account doesn't exist or is malformedInsufficient balance: Your Squad balance is insufficientAccount closed: Beneficiary account is closedDaily limit exceeded: You've reached your daily payout limitBank error: Temporary banking system issue (will retry automatically)Important Notes:
Lookup and validate a bank account before initiating a payout. This prevents sending money to incorrect accounts.
Endpoint: POST /payout/account/lookup
Request Body:
{
"bank_code": "058",
"account_number": "0123456789"
}
Field Definitions:
bank_code (required, string): 3-digit CBN bank codeaccount_number (required, string): 10-digit NUBAN account numberResponse (Valid Account):
{
"status": 200,
"success": true,
"data": {
"bank_code": "058",
"account_number": "0123456789",
"account_name": "Chinedu Adeyemi",
"account_type": "Individual",
"verified": true
}
}
Response (Invalid Account):
{
"status": 400,
"success": false,
"message": "Account number not found"
}
Account Type Values:
Individual: Personal bank accountBusiness: Business/Corporate bank accountGovernment: Government accountNGO: Non-profit organization accountImportant Notes:
account_name in your payout request to ensure accuracyRetrieve your transaction history for reconciliation, reporting, and audit purposes.
Endpoint: GET /transaction/list
Query Parameters:
page (optional, integer): Page number (starts at 1, defaults to 1)per_page (optional, integer): Results per page (max 100, defaults to 50)from (optional, string): Start date in YYYY-MM-DD format (inclusive)to (optional, string): End date in YYYY-MM-DD format (inclusive)status (optional, string): Filter by status (pending, processed, failed)type (optional, string): Filter by transaction type (payment, payout, virtual_account)Example Request:
GET /transaction/list?page=1&per_page=25&from=2025-02-01&to=2025-02-28&status=processed
Response:
{
"status": 200,
"success": true,
"data": {
"page": 1,
"per_page": 25,
"total": 156,
"total_pages": 7,
"transactions": [
{
"transaction_id": "TXN_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"transaction_ref": "TXN-2025-001",
"type": "payment",
"amount": 50000,
"currency": "NGN",
"status": "processed",
"customer_email": "[email protected]",
"payment_method": "card",
"created_at": "2025-02-24T10:30:00Z",
"completed_at": "2025-02-24T10:35:00Z"
},
{
"transaction_id": "TXN_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"transaction_ref": "PAYOUT-2025-001",
"type": "payout",
"amount": 100000,
"currency": "NGN",
"status": "success",
"created_at": "2025-02-24T11:00:00Z",
"completed_at": "2025-02-24T11:02:30Z"
}
]
}
}
Response Fields:
page: Current page numberper_page: Results returned per pagetotal: Total transaction count matching filterstotal_pages: Number of pages availabletransactions: Array of transaction objectsImportant Notes:
Set up recurring debits from a customer's bank account with their authorization. Mandates enable subscription billing and automated payments.
Endpoint: POST /transaction/mandate/create
Request Body:
{
"customer_email": "[email protected]",
"customer_name": "Amina Okafor",
"bank_code": "058",
"account_number": "0123456789",
"account_name": "Amina Okafor",
"amount": 50000,
"start_date": "2025-03-01",
"mandate_ref": "MANDATE-2025-001",
"meta_data": {
"subscription_id": "SUB-12345",
"plan": "premium_monthly"
}
}
Field Definitions:
customer_email (required, string): Customer's email for mandate notificationscustomer_name (required, string): Full name of customerbank_code (required, string): 3-digit CBN bank code of customer's accountaccount_number (required, string): 10-digit NUBAN account numberaccount_name (required, string): Account name (must match bank records)amount (required, integer): Amount per debit in kobostart_date (optional, string): Mandate activation date (YYYY-MM-DD format)mandate_ref (required, string): Your unique mandate referenceResponse:
{
"status": 200,
"success": true,
"data": {
"mandate_id": "MAND_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"mandate_ref": "MANDATE-2025-001",
"customer_email": "[email protected]",
"bank_code": "058",
"account_number": "0123456789",
"amount": 50000,
"status": "pending_activation",
"created_at": "2025-02-24T12:00:00Z"
}
}
Mandate Status Values:
pending_activation: Awaiting customer activation (via email/SMS link)active: Mandate is active and ready for debitssuspended: Temporarily pausedrevoked: Cancelled by customer or merchantImportant Notes:
Debit a customer's account using an active mandate.
Endpoint: POST /transaction/mandate/debit
Request Body:
{
"mandate_ref": "MANDATE-2025-001",
"amount": 50000,
"transaction_ref": "DEBIT-2025-001",
"narration": "Monthly subscription payment"
}
Field Definitions:
mandate_ref (required, string): Reference of the activated mandateamount (required, integer): Amount to debit in kobo (must not exceed mandate limit)transaction_ref (required, string): Unique reference for this debit transactionnarration (optional, string): Description for the customer's statementResponse:
{
"status": 200,
"success": true,
"data": {
"debit_id": "DEBIT_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"transaction_ref": "DEBIT-2025-001",
"mandate_ref": "MANDATE-2025-001",
"amount": 50000,
"status": "initiated",
"initiated_at": "2025-02-24T12:15:00Z"
}
}
Important Notes:
active status for debits to succeedSquad sends real-time webhooks for transaction and payout events. This enables you to react immediately to payment status changes without polling.
https://yourdomain.com/webhooks/squad)Squad sends the following webhook events:
| Event | Trigger | Payload Contains |
|---|---|---|
charge_successful | Payment completed successfully | Transaction ID, reference, amount, payment method |
charge_failed | Payment attempt failed | Reason for failure, transaction reference |
payout_successful | Payout transfer completed | Payout reference, amount, beneficiary details |
payout_failed | Payout transfer failed | Failure reason, payout reference |
virtual_account.funded | Money deposited to virtual account | Account ID, amount, sender, deposit reference |
mandate.created | New mandate created | Mandate ID, customer email, status |
mandate.activated | Mandate activated by customer | Mandate ID, activation timestamp |
mandate.revoked | Mandate cancelled | Mandate ID, reason |
Example: charge_successful Event
{
"event": "charge_successful",
"data": {
"transaction_id": "TXN_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"transaction_ref": "TXN-2025-001",
"amount": 50000,
"currency": "NGN",
"status": "processed",
"customer_email": "[email protected]",
"customer_name": "Amina Okafor",
"payment_method": "card",
"paid_at": "2025-02-24T10:35:00Z",
"meta_data": {
"order_id": "ORD-12345"
}
},
"timestamp": "2025-02-24T10:35:05Z"
}
Example: payout_successful Event
{
"event": "payout_successful",
"data": {
"reference": "PAYOUT-2025-001",
"transaction_id": "TXN_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f",
"amount": 100000,
"currency": "NGN",
"bank_code": "058",
"account_number": "0123456789",
"account_name": "Chinedu Adeyemi",
"status": "success",
"completed_at": "2025-02-24T11:02:30Z"
},
"timestamp": "2025-02-24T11:02:35Z"
}
Squad signs all webhooks using HMAC-SHA512. Verify the signature in the x-squad-signature header to ensure authenticity.
Node.js/Express Example:
const crypto = require('crypto');
app.post('/webhooks/squad', (req, res) => {
const signature = req.headers['x-squad-signature'];
const secret = process.env.SQUAD_WEBHOOK_SECRET;
// Create HMAC-SHA512 hash of the request body
const hash = crypto
.createHmac('sha512', secret)
.update(JSON.stringify(req.body), 'utf8')
.digest('hex');
// Compare signatures
if (hash !== signature) {
console.error('Invalid webhook signature');
return res.status(403).json({ error: 'Unauthorized' });
}
// Signature is valid; process the webhook
const event = req.body.event;
const data = req.body.data;
if (event === 'charge_successful') {
// Handle successful payment
console.log(`Payment confirmed: ${data.transaction_ref}`);
// Update database, fulfill order, etc.
} else if (event === 'payout_successful') {
// Handle successful payout
console.log(`Payout completed: ${data.reference}`);
}
res.json({ status: 'received' });
});
Python/Flask Example:
from flask import request
import hmac
import hashlib
import json
@app.route('/webhooks/squad', methods=['POST'])
def squad_webhook():
signature = request.headers.get('x-squad-signature')
secret = os.environ.get('SQUAD_WEBHOOK_SECRET')
# Create HMAC-SHA512 hash
computed_hash = hmac.new(
secret.encode(),
request.data,
hashlib.sha512
).hexdigest()
# Verify signature
if not hmac.compare_digest(computed_hash, signature):
return {'error': 'Unauthorized'}, 403
# Process webhook
payload = request.json
event = payload.get('event')
data = payload.get('data')
if event == 'charge_successful':
handle_payment_success(data)
elif event == 'payout_successful':
handle_payout_success(data)
return {'status': 'received'}, 200
Accept payments from online store customers.
1. Customer adds items to cart and clicks checkout
2. POST /transaction/initiate with order details
3. Receive checkout_url; redirect customer or send via email
4. Customer completes payment on Squad checkout page
5. Receive webhook: charge_successful
6. Verify transaction via GET /transaction/verify/{ref}
7. Confirm status is "processed"
8. Fulfill order and send confirmation email
Code Example (Node.js):
const axios = require('axios');
async function initiatePayment(order) {
const response = await axios.post(
'https://api-d.squadco.com/transaction/initiate',
{
amount: order.total_amount_kobo,
email: order.customer_email,
currency: 'NGN',
initiate_type: 'inline',
transaction_ref: `ORD-${order.id}`,
customer_name: order.customer_name,
meta_data: {
order_id: order.id,
items: order.items.length,
shipping_address: order.address
},
pass_charge: false
},
{
headers: {
Authorization: `Bearer ${process.env.SQUAD_API_KEY}`
}
}
);
return response.data.data.checkout_url;
}
// Webhook handler
app.post('/webhooks/squad', async (req, res) => {
const { event, data } = req.body;
if (event === 'charge_successful') {
const orderId = data.meta_data.order_id;
// Update order status
await db.order.update(
{ id: orderId },
{ status: 'paid', paid_at: new Date() }
);
// Trigger fulfillment
await fulfillOrder(orderId);
// Send confirmation
await sendConfirmationEmail(data.customer_email, orderId);
}
res.json({ status: 'received' });
});
Process payments to multiple sellers based on their sales.
1. Multiple buyers make purchases (each initiates payment)
2. Receive charge_successful webhooks for each payment
3. After order fulfillment (e.g., 7 days):
a. GET /payout/verify to validate seller's bank account
b. Calculate seller's commission (total - platform fee)
c. POST /payout/transfer to send commission
d. Receive payout_successful webhook
4. Log payout in seller dashboard
5. Send payout confirmation to seller
Code Example (Python):
async def process_seller_payout(seller_id, orders):
seller = await db.seller.get(seller_id)
# Calculate total sales
total_sales = sum(o.amount for o in orders)
# Platform takes 10% commission
commission = int(total_sales * 0.10)
payout_amount = total_sales - commission
# Verify seller's bank account
verify_response = requests.get(
f'{SQUAD_BASE_URL}/payout/account/lookup',
params={
'bank_code': seller.bank_code,
'account_number': seller.account_number
},
headers={'Authorization': f'Bearer {SQUAD_API_KEY}'}
)
if not verify_response.json()['success']:
raise Exception('Account verification failed')
# Initiate payout
payout_response = requests.post(
f'{SQUAD_BASE_URL}/payout/transfer',
json={
'amount': payout_amount,
'currency': 'NGN',
'bank_code': seller.bank_code,
'account_number': seller.account_number,
'account_name': verify_response.json()['data']['account_name'],
'reference': f'PAYOUT-{seller_id}-{int(time.time())}',
'narration': f'Sales commission - {len(orders)} orders'
},
headers={'Authorization': f'Bearer {SQUAD_API_KEY}'}
)
payout = payout_response.json()['data']
# Store payout record
await db.payout.create({
seller_id: seller_id,
amount: payout_amount,
reference: payout['reference'],
status: 'initiated'
})
return payout
Create unique virtual accounts for vendors to receive payments from customers.
1. Vendor signs up on your platform
2. POST /virtual-account to create dedicated GTBank account
3. Share account number with vendor
4. Customers transfer money directly to vendor's account
5. Funds appear in your merchant balance immediately
6. GET /transaction/list to track virtual account deposits
7. Optional: Auto-sweep to vendor's own bank account
Code Example:
async function createVendorAccount(vendor) {
// Create virtual account
const response = await axios.post(
'https://api-d.squadco.com/virtual-account',
{
customer_name: vendor.business_name,
email: vendor.email,
phone: vendor.phone,
meta_data: {
vendor_id: vendor.id,
onboarding_date: new Date().toISOString()
}
},
{
headers: {
Authorization: `Bearer ${process.env.SQUAD_API_KEY}`
}
}
);
const account = response.data.data;
// Store account details
await db.vendor.update(
{ id: vendor.id },
{
virtual_account_id: account.virtual_account_id,
virtual_account_number: account.account_number,
virtual_account_bank: 'Guaranty Trust Bank',
virtual_account_code: account.bank_code,
account_status: 'active'
}
);
// Send account details to vendor
await sendEmailWithAccountDetails(vendor.email, account);
return account;
}
// Monitor deposits to virtual accounts
async function syncVirtualAccountTransactions() {
const vendors = await db.vendor.findAll({
where: { virtual_account_id: { $ne: null } }
});
for (const vendor of vendors) {
const txnResponse = await axios.get(
'https://api-d.squadco.com/virtual-account/customer/transactions/' +
vendor.virtual_account_id,
{
headers: {
Authorization: `Bearer ${process.env.SQUAD_API_KEY}`
}
}
);
const transactions = txnResponse.data.data;
// Process each transaction
for (const txn of transactions) {
if (!await db.virtualAccountDeposit.findOne({
where: { transaction_id: txn.transaction_id }
})) {
// New deposit
await db.virtualAccountDeposit.create({
vendor_id: vendor.id,
transaction_id: txn.transaction_id,
amount: txn.amount,
deposited_at: txn.created_at
});
// Credit vendor's balance
await creditVendorBalance(vendor.id, txn.amount);
}
}
}
}
Set up recurring subscription payments using direct debits.
1. Customer subscribes to plan (e.g., monthly software service)
2. GET /transaction/mandate/banklists to show supported banks
3. Customer provides bank account details
4. POST /transaction/mandate/create to create mandate
5. Customer receives activation email from Squad
6. Customer clicks activation link and authorizes mandate
7. Mandate status changes to "active"
8. Monthly: POST /transaction/mandate/debit to charge subscription
9. Receive webhook confirmation of debit
10. Update subscription status and renew service
Code Example:
// Step 1: Create mandate
async function createSubscriptionMandate(customer, plan) {
const mandateResponse = await axios.post(
'https://api-d.squadco.com/transaction/mandate/create',
{
customer_email: customer.email,
customer_name: customer.name,
bank_code: customer.bank_code,
account_number: customer.account_number,
account_name: customer.account_name,
amount: plan.monthly_amount_kobo,
mandate_ref: `MANDATE-${customer.id}-${plan.id}`,
start_date: new Date().toISOString().split('T')[0],
meta_data: {
customer_id: customer.id,
plan_id: plan.id,
plan_name: plan.name
}
},
{
headers: {
Authorization: `Bearer ${process.env.SQUAD_API_KEY}`
}
}
);
const mandate = mandateResponse.data.data;
// Store mandate
await db.subscription.create({
customer_id: customer.id,
plan_id: plan.id,
mandate_id: mandate.mandate_id,
mandate_ref: mandate.mandate_ref,
status: 'pending_activation',
created_at: new Date()
});
return mandate;
}
// Step 2: Process monthly renewals
async function processMonthlyRenewals() {
const activeSubscriptions = await db.subscription.findAll({
where: { status: 'active', last_billed_at: { $lt: Date.now() - 30*24*60*60*1000 } }
});
for (const sub of activeSubscriptions) {
const plan = await db.plan.get(sub.plan_id);
try {
const debitResponse = await axios.post(
'https://api-d.squadco.com/transaction/mandate/debit',
{
mandate_ref: sub.mandate_ref,
amount: plan.monthly_amount_kobo,
transaction_ref: `DEBIT-${sub.id}-${Date.now()}`,
narration: `${plan.name} subscription renewal`
},
{
headers: {
Authorization: `Bearer ${process.env.SQUAD_API_KEY}`
}
}
);
// Update subscription
await db.subscription.update(
{ id: sub.id },
{ last_billed_at: new Date() }
);
console.log(`Subscription renewal initiated: ${sub.id}`);
} catch (error) {
console.error(`Subscription renewal failed: ${sub.id}`, error.message);
await notifyCustomerOfFailedRenewal(sub.customer_id);
}
}
}
// Webhook handler for mandate debits
app.post('/webhooks/squad', async (req, res) => {
const { event, data } = req.body;
if (event === 'charge_successful' && data.meta_data?.type === 'mandate_debit') {
const subscription = await db.subscription.findOne({
where: { mandate_ref: data.meta_data.mandate_ref }
});
if (subscription) {
// Update subscription expiry
await db.subscription.update(
{ id: subscription.id },
{ expires_at: new Date(Date.now() + 30*24*60*60*1000) }
);
await sendRenewalConfirmation(subscription.customer_id);
}
}
res.json({ status: 'received' });
});
Squad returns structured error responses. Handle errors gracefully and implement appropriate retry logic.
HTTP 4xx Error (Client Error):
{
"status": 400,
"success": false,
"message": "Invalid bank code provided"
}
HTTP 5xx Error (Server Error):
{
"status": 500,
"success": false,
"message": "Internal server error. Please try again later."
}
| Code | Error Type | Meaning | Action |
|---|---|---|---|
| 200 | Success | Request successful | Process normally |
| 400 | Bad Request | Invalid parameters, missing fields | Fix request and retry immediately |
| 401 | Unauthorized | Invalid or missing API key | Check Bearer token in headers |
| 404 | Not Found | Resource doesn't exist | Verify transaction/account reference |
| 429 | Rate Limited | Exceeded API rate limit | Implement exponential backoff |
| 500 | Server Error | Squad service issue | Retry with exponential backoff |
| 502 | Bad Gateway | Temporary connectivity issue | Retry with exponential backoff |
| 503 | Service Unavailable | Squad maintenance or overload | Retry with exponential backoff |
Payment Initiation:
"amount is required" — Missing amount field"amount must be greater than 0" — Amount is zero or negative"Invalid email format" — Customer email is invalid"Duplicate transaction reference" — Reference already processed in last 24 hoursPayout:
"Insufficient balance" — Your Squad merchant balance is too low"Invalid bank code" — Bank code doesn't exist or is malformed"Invalid account number" — Account number format is wrong (must be 10 digits)"Account not found" — Account doesn't exist at specified bank"Daily limit exceeded" — You've reached your daily payout limitVirtual Account:
"KYC not approved" — Your business KYC is not verified for account creation"Account creation limit reached" — You've created the maximum allowed accountsMandate/Direct Debit:
"Mandate not found" — Mandate reference doesn't exist"Mandate not active" — Mandate hasn't been activated by customer yet"Amount exceeds mandate limit" — Debit amount exceeds authorized limitImplement exponential backoff for retryable errors (429, 5xx):
async function apiRequestWithRetry(fn, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Don't retry 4xx errors except 429
if (error.response?.status >= 400 && error.response?.status < 500 && error.response?.status !== 429) {
throw error;
}
// Calculate backoff: 1s, 2s, 4s
const delayMs = Math.pow(2, attempt) * 1000;
console.log(`Attempt ${attempt + 1} failed. Retrying in ${delayMs}ms...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
throw lastError;
}
// Usage
const result = await apiRequestWithRetry(async () => {
return axios.post(`${SQUAD_BASE_URL}/payout/transfer`, payoutData, {
headers: { Authorization: `Bearer ${SQUAD_API_KEY}` }
});
});
All amounts are in kobo, not Naira.
This is the most common integration error. Always multiply Nigerian Naira by 100 before sending to Squad APIs.
Use globally unique transaction references to ensure idempotency:
TXN-${Date.now()}-${uuid()}Always use valid 3-digit CBN bank codes:
Invalid codes cause payout failures. Use the complete bank code list from CBN documentation.
Always validate recipient accounts before payouts:
// Never skip this step
const verifyResponse = await axios.get(
'https://api-d.squadco.com/payout/account/lookup?bank_code=058&account_number=0123456789',
{ headers: { Authorization: `Bearer ${SQUAD_API_KEY}` } }
);
if (!verifyResponse.data.success) {
throw new Error('Account validation failed');
}
// Use the verified account name in your payout
const accountName = verifyResponse.data.data.account_name;
Always verify webhook signatures using HMAC-SHA512:
x-squad-signature headerThe API response to POST /transaction/initiate indicates request acceptance, not payment completion:
checkout_url with customercharge_successful webhook"processed" before shipping/deliveringhttps://sandbox-api-d.squadco.com — Use for testinghttps://api-d.squadco.com — Use for real transactionssandbox_sk_)Squad enforces API rate limits to protect infrastructure:
Retry-After header indicating wait timeFor instant settlement when using virtual accounts:
Check your dashboard for:
Large payouts may be queued or require manual approval.
When a customer sends funds into a Squad virtual account, Squad fires a webhook to your configured URL. This is the primary way to know a virtual account has been funded — do not poll.
{
"Event": "charge_successful",
"TransactionRef": "SQVA_xxxxxxxxxxxx",
"Body": {
"transaction_ref": "SQVA_xxxxxxxxxxxx",
"virtual_account_number": "2XXXXXXXXXX",
"principal_amount": "5000",
"settled_amount": "4975",
"fee_charged": "25",
"transaction_date": "2024-02-15T10:35:22.000Z",
"customer_identifier": "your_unique_customer_ref",
"transaction_indicator": "C",
"remarks": "Transfer from Emeka Okafor",
"currency": "NGN",
"channel": "virtual_account",
"bank_name": "Guaranty Trust Bank"
}
}
Verify the webhook signature as documented in the main webhook section before processing. The customer_identifier maps to the reference you passed when creating the virtual account — use this to match the payment to your customer record.
Squad provides endpoints to manage chargebacks and payment disputes raised by customers through their banks.
GET /api/v1/dispute
Authorization: Bearer {secret_key}
Returns all disputes on your merchant account with status: pending, awaiting_response, resolved, won, lost.
GET /api/v1/dispute/{ticket_id}
Authorization: Bearer {secret_key}
POST /api/v1/dispute/{ticket_id}/upload-evidence
Authorization: Bearer {secret_key}
Content-Type: multipart/form-data