npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@padar-labs/squad-ts

v1.0.0

Published

TypeScript SDK for the Squad payment gateway (squadco.com)

Readme

@padar-labs/squad-ts

A comprehensive TypeScript SDK for the Squad payment gateway — fully typed, zero dependencies, dual ESM/CJS, and covering every documented API endpoint.

npm version License: MIT TypeScript


Table of Contents


Installation

npm install @padar-labs/squad-ts
# or
yarn add @padar-labs/squad-ts
# or
pnpm add @padar-labs/squad-ts

Quick Start

import { Squad } from "@padar-labs/squad-ts";

const squad = new Squad({
  secretKey: "sandbox_sk_your_key_here",
  environment: "sandbox", // 'sandbox' | 'live' — defaults to 'live'
  timeout: 30000,         // optional, ms — defaults to 30000
});

// Initiate a payment
const payment = await squad.payments.initiate({
  email: "[email protected]",
  amount: 100000, // in kobo (100000 kobo = ₦1,000)
  currency: "NGN",
  initiate_type: "inline",
  transaction_ref: "TXN_" + Date.now(),
  callback_url: "https://yourapp.com/payment/callback",
});

console.log(payment.data.checkout_url);
// → https://checkout.squadco.com/pay/...

Configuration

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | secretKey | string | Yes | — | Your Squad secret key (sandbox_sk_... or sk_...) | | environment | 'sandbox' \| 'live' | No | 'live' | API environment | | timeout | number | No | 30000 | Request timeout in milliseconds |

| Environment | Base URL | |-------------|----------| | sandbox | https://sandbox-api-d.squadco.com | | live | https://api-d.squadco.com |


API Reference

squad.payments

initiate(payload)

Initiates a payment and returns a checkout URL to redirect or embed.

const response = await squad.payments.initiate({
  email: "[email protected]",
  amount: 100000,           // required, in kobo
  currency: "NGN",          // optional
  initiate_type: "inline",  // 'inline' | 'redirect'
  transaction_ref: "TXN_001",
  callback_url: "https://yourapp.com/callback",
  payment_channels: ["card", "bank_transfer", "ussd"],
  pass_charge: false,       // pass fees to customer
  metadata: { orderId: "ORD_001" },
});

// response.data.checkout_url — redirect customer here

verify(transactionRef)

Verifies a transaction by reference. This is a read-only status check — it does not trigger any payment processing or notifications.

const result = await squad.payments.verify("TXN_001");

console.log(result.data.transaction_status); // 'Success' | 'Failed' | 'Abandoned' | 'Pending'
console.log(result.data.transaction_amount); // amount in kobo

getTransaction(transactionRef)

Convenience wrapper around verify — returns data directly (unwrapped from the envelope).

const txn = await squad.payments.getTransaction("TXN_001");
console.log(txn.email);
console.log(txn.transaction_status);

getAllTransactions(params)

Get all transactions with filters. Date range is required and must be within one month.

const result = await squad.payments.getAllTransactions({
  page: 1,
  perPage: 20,
  currency: "NGN",
  start_date: "2026-05-01",
  end_date: "2026-05-31",
});

result.data.rows.forEach((txn) => console.log(txn.transaction_ref));

charge(payload) — Direct API

Charge a card directly without the hosted checkout page.

const result = await squad.payments.charge({
  transaction_reference: "DIRECT_001",
  amount: 10000,
  currency: "NGN",
  payment_method: "card",
  card: {
    number: "5061000000000000000",
    cvv: "123",
    expiry_month: "12",
    expiry_year: "26",
  },
  customer: { name: "John Doe", email: "[email protected]" },
});

// result.data.auth_model: 'ValidatePin' | 'ValidateOTP' | 'ThreeDSecure'

authorize(payload) — Direct API PIN/OTP

Submit a PIN or OTP to complete a direct charge flow.

await squad.payments.authorize({
  transaction_reference: "DIRECT_001",
  authorization: { pin: "1234" }, // or { otp: "123456" }
});

verifyPos(transactionReference)

Verify a POS/SoftPOS transaction.

const result = await squad.payments.verifyPos("POS_TXN_001");

squad.transfers

accountLookup(payload)

Resolve an account holder's name before sending a transfer. Always do this first and use the returned name in initiateTransfer.

const result = await squad.transfers.accountLookup({
  bank_code: "058",
  account_number: "0123456789",
});

console.log(result.data.account_name); // 'JOHN DOE'

initiateTransfer(payload)

Send money to a bank account.

Important: The transaction_reference must be prefixed with your merchant ID (e.g. "MERCHANTID_uniqueref"). If you receive a 424 timeout error, call requery() with the same reference before retrying — never reuse a timed-out reference with a new one.

const result = await squad.transfers.initiateTransfer({
  transaction_reference: "MERCHANTID_TRF001",
  amount: "100000",     // in kobo
  bank_code: "058",
  account_number: "0123456789",
  account_name: "JOHN DOE", // use the result from accountLookup
  currency_id: "NGN",
  remark: "Payment for services",
});

console.log(result.data.nip_transaction_reference);

requery(payload)

Re-query the status of a transfer. Use this after a 424 timeout before deciding to retry.

const status = await squad.transfers.requery({
  transaction_reference: "MERCHANTID_TRF001",
});

getAllTransfers(params)

const transfers = await squad.transfers.getAllTransfers({
  page: 1,
  perPage: 50,
  dir: "DESC",
});

getBanks()

Get the list of all supported banks and their codes.

const { data } = await squad.transfers.getBanks();
data.forEach((bank) => console.log(bank.bank_code, bank.bank_name));

squad.virtualAccounts

create(payload)

Create a static virtual account for a customer (tied to GTBank).

const account = await squad.virtualAccounts.create({
  first_name: "John",
  last_name: "Doe",
  middle_name: "Paul",
  mobile_num: "08012345678",
  dob: "01/01/1990",              // mm/dd/yyyy
  email: "[email protected]",
  bvn: "22222222222",
  gender: "1",                    // '1' = Male, '2' = Female
  address: "1 Test Street, Lagos",
  customer_identifier: "CUST_001", // your unique customer ID
  beneficiary_account: "0123456789", // 10-digit GTBank account
});

console.log(account.data.virtual_account_number);

createBusiness(payload)

Create a static virtual account for a business.

const bizAccount = await squad.virtualAccounts.createBusiness({
  bvn: "22222222222",
  business_name: "Techzilla Ltd",
  customer_identifier: "BIZ_001",
  mobile_num: "08012345678",
  beneficiary_account: "0123456789",
});

getCustomerTransactions(customer_identifier)

Get all incoming transactions for a specific customer.

const txns = await squad.virtualAccounts.getCustomerTransactions("CUST_001");

getMerchantTransactions()

Get all incoming virtual account transactions across your merchant profile.

getMerchantTransactionsFiltered(params)

const result = await squad.virtualAccounts.getMerchantTransactionsFiltered({
  page: 1,
  perPage: 20,
  startDate: "01-01-2024",  // MM-DD-YYYY
  endDate: "12-31-2024",
  dir: "DESC",
});

getCustomerByVirtualAccount(virtual_account_number)

Retrieve a customer's details using their virtual account number.

const customer = await squad.virtualAccounts.getCustomerByVirtualAccount("7834927713");

getCustomerByIdentifier(customer_identifier)

Retrieve a customer's details using the identifier you assigned when creating their account.

const customer = await squad.virtualAccounts.getCustomerByIdentifier("CUST001");

getMerchantAccounts(params?)

List all virtual accounts created under your merchant profile.

const accounts = await squad.virtualAccounts.getMerchantAccounts({ page: 1, perPage: 10 });
console.log(accounts.data.count);
console.log(accounts.data.rows);

simulatePayment(payload) — sandbox only

Simulate an incoming payment to a virtual account for testing.

await squad.virtualAccounts.simulatePayment({
  virtual_account_number: "7834927713",
  amount: "10000",
});

initiateDynamic(payload) — Dynamic Virtual Accounts

Create a one-time virtual account that expires after a set duration or once the expected amount is received.

const dva = await squad.virtualAccounts.initiateDynamic({
  amount: 50000,
  duration: 3600, // seconds until expiry
  email: "[email protected]",
  transaction_ref: "DVA_001",
});

console.log(dva.data.account_number);
console.log(dva.data.expires_at);

updateDynamic(payload)

Update the amount or duration of an existing dynamic virtual account.

await squad.virtualAccounts.updateDynamic({
  transaction_reference: "DVA_001",
  amount: 60000,
  duration: 7200,
});

getDynamicTransactions(transaction_reference)

const txns = await squad.virtualAccounts.getDynamicTransactions("DVA_001");
// txns.data.rows[0].transaction_status: 'SUCCESS' | 'EXPIRED' | 'MISMATCH'

squad.recurring

createSubscriptionPlan(payload)

Create a recurring billing plan.

const plan = await squad.recurring.createSubscriptionPlan({
  name: "Monthly Premium",
  amount: 5000, // kobo
  interval: "monthly", // 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'yearly'
  description: "Access to premium features",
});

console.log(plan.data.plan_code); // save this

updateSubscriptionPlan(payload)

await squad.recurring.updateSubscriptionPlan({
  plan_code: "PLAN_001",
  name: "Monthly Premium Plus",
  amount: 7500,
});

getAllSubscriptionPlans()

Retrieve all subscription plans under your merchant account.

const plans = await squad.recurring.getAllSubscriptionPlans();
console.log(plans.data); // array of plans

getSubscriptionPlan(plan_code)

Retrieve a single subscription plan by its plan code.

const plan = await squad.recurring.getSubscriptionPlan("PLAN_001");
console.log(plan.data.name);

customerSubscription(payload)

Subscribe a customer to a plan. Requires a tokenized card token_id obtained from a prior payment.

await squad.recurring.customerSubscription({
  plan_code: "PLAN_001",
  email: "[email protected]",
  token_id: "TOKEN_FROM_PAYMENT",
});

cancelSubscription(payload)

await squad.recurring.cancelSubscription({
  subscription_code: "SUB_001",
  email: "[email protected]",
  token_id: "TOKEN_FROM_PAYMENT",
});

getAllSubscriptions()

Retrieve all subscriptions (active and cancelled) under your merchant account.

const subscriptions = await squad.recurring.getAllSubscriptions();
console.log(subscriptions.data); // array of subscriptions

getSubscription(subscription_code)

Retrieve a single subscription by its code.

const subscription = await squad.recurring.getSubscription("SUB_001");
console.log(subscription.data.status); // "active" | "cancelled"

squad.refunds

initiate(payload)

Initiate a full or partial refund on a completed transaction.

const refund = await squad.refunds.initiate({
  gateway_transaction_ref: "GW_REF_001", // from webhook payload
  transaction_ref: "TXN_REF_001",
  refund_type: "Full",                   // 'Full' | 'Partial'
  reason_for_refund: "Customer request",
  // refund_amount: "5000",              // required only for Partial (in kobo)
});

console.log(refund.data.refund_reference);

getAll(params?)

const refunds = await squad.refunds.getAll({ page: 1, perPage: 10 });

update(payload)

await squad.refunds.update({
  gateway_transaction_ref: "GW_REF_001",
  action: "accept",
});

squad.disputes

getAll()

Retrieve all disputes raised against your transactions.

const disputes = await squad.disputes.getAll();

getUploadUrl(ticket_id, file_name)

Get a pre-signed S3 URL to upload evidence when rejecting a dispute.

const { data } = await squad.disputes.getUploadUrl("TICKET_001", "evidence.pdf");
// Upload your file to data.upload_url, then call resolve()

resolve(ticket_id, payload)

Accept or reject a dispute.

await squad.disputes.resolve("TICKET_001", {
  action: "rejected", // 'accepted' | 'rejected'
  file_name: "evidence.pdf",
});

squad.vas

purchaseAirtime(payload)

const result = await squad.vas.purchaseAirtime({
  phone_number: "08012345678",
  amount: 100, // Naira (minimum ₦50)
});

purchaseData(payload)

await squad.vas.purchaseData({
  phone_number: "08012345678",
  plan_code: "1001", // from getDataBundles()
  amount: 1000,
});

getDataBundles(network)

const bundles = await squad.vas.getDataBundles("MTN");
// bundles.data: [{ plan_name, bundle_value, bundle_price, plan_code, ... }]

getMobileNetworks()

Returns the list of supported networks: MTN | GLO | AIRTEL | 9MOBILE.

const { networks } = await squad.vas.getMobileNetworks();

getTransactions(params)

await squad.vas.getTransactions({ page: 1, perPage: 20, action: "debit" });

squad.paymentLinks

create(payload)

Create a shareable payment link.

const link = await squad.paymentLinks.create({
  name: "Pay for invoice #123",
  description: "Payment for consulting services",
  amount: 150000, // optional — omit for open-amount links
  redirect_link: "https://yourapp.com/success",
  support_email: "[email protected]",
});

// Share link.data.checkout_url with your customer

update(id, payload)

await squad.paymentLinks.update("LINK_001", { amount: 200000 });

getAll()

Retrieve all payment links under your merchant account.

const links = await squad.paymentLinks.getAll();
console.log(links.data); // array of payment links

getOne(id)

Retrieve a single payment link by its ID.

const link = await squad.paymentLinks.getOne("LINK_001");
console.log(link.data.hash); // the link slug

toggleStatus(id)

Enable or disable a payment link.

await squad.paymentLinks.toggleStatus("LINK_001");

squad.webhooks

Verifying Checkout Webhook Signatures

Squad sends an x-squad-encrypted-body header with every webhook. Always validate it before processing. Use validateWebhook if you manage the secret key yourself, or validateCheckoutWebhook to use the key already configured on the client instance.

// Express example
app.post("/webhook/squad", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-squad-encrypted-body"] as string;
  const isValid = squad.webhooks.validateWebhook(
    req.body,
    signature,
    process.env.SQUAD_SECRET_KEY!,
  );

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = squad.webhooks.parseEvent(JSON.parse(req.body as unknown as string));

  // Always check your DB first to avoid processing the same transaction twice
  if (event.Event === "charge_successful") {
    const { transaction_ref, amount, email } = event.Body;
    // Fulfill order, credit wallet, etc.
  }

  // Respond immediately with an acknowledgment
  const ack = squad.webhooks.buildAcknowledgment(event.TransactionRef, 200);
  res.json(ack);
});

Virtual Account Webhook Validation

const isValid = squad.webhooks.validateVirtualAccountWebhook(
  payload,           // parsed webhook body
  signature,         // x-squad-signature header
  "your_secret_key",
);

Squad uses two hashing schemes depending on the webhook version:

| Version | Hash input | |---------|-----------| | v1 | Entire JSON body | | v2/v3 | transaction_reference\|virtual_account_number\|currency\|principal_amount\|settled_amount\|customer_identifier |

buildAcknowledgment(transaction_reference, status?)

Build the required response Squad expects after receiving a webhook.

res.json(squad.webhooks.buildAcknowledgment("TXN_001", 200));
// { response_code: 200, transaction_reference: "TXN_001", response_description: "Success" }

Error Handling

All API errors throw a SquadError, which extends the native Error class.

import { Squad, SquadError } from "@padar-labs/squad-ts";

try {
  await squad.payments.verify("INVALID_REF");
} catch (err) {
  if (err instanceof SquadError) {
    console.error(err.message);     // human-readable message from Squad
    console.error(err.statusCode);  // HTTP status code
    console.error(err.data);        // raw Squad API error body
  }
}

Error Reference

| Scenario | statusCode | Example message | |----------|-------------|-------------------| | Invalid request payload | 400 | "Invalid transaction reference" | | Missing or bad API key | 401 | "Unauthorized" | | Wrong environment key | 403 | "API key is invalid. Key must start with sandbox_sk_" | | Request timeout | 408 | "Request timed out" | | Transfer timeout | 424 | "Transaction timed out" | | Network failure | 0 | "Network error — check your connection" |


TypeScript Usage

All request/response types are exported and can be imported directly:

import type {
  InitiatePaymentPayload,
  InitiatePaymentResponse,
  VerifyTransactionData,
  InitiateTransferPayload,
  CreateCustomerVirtualAccountPayload,
  VirtualAccountTransaction,
  WebhookEvent,
  VirtualAccountWebhookPayload,
  SubscriptionPlan,
  DataBundle,
  MobileNetwork,
} from "@padar-labs/squad-ts";

Typed Webhook Handlers

import type { WebhookEvent, VirtualAccountWebhookPayload } from "@padar-labs/squad-ts";

function handleCheckoutWebhook(event: WebhookEvent) {
  if (event.Event === "charge_successful") {
    const { transaction_type, amount, email } = event.Body;
    // transaction_type: 'Card' | 'Transfer' | 'Bank' | 'Ussd' | 'MerchantUssd'
  }
}

function handleVirtualAccountWebhook(payload: VirtualAccountWebhookPayload) {
  const { transaction_reference, principal_amount, sender_name } = payload;
}

Environment Variables

Copy .env.example to .env and fill in your keys:

SQUAD_SECRET_KEY=sandbox_sk_your_key_here
SQUAD_ENVIRONMENT=sandbox

Then load with dotenv:

import { Squad } from "@padar-labs/squad-ts";
import "dotenv/config";

const squad = new Squad({
  secretKey: process.env.SQUAD_SECRET_KEY!,
  environment: (process.env.SQUAD_ENVIRONMENT as "sandbox" | "live") ?? "sandbox",
});

Key Business Rules

  1. Transfer references must be prefixed with your merchant ID — e.g. "MERCHANTID_uniqueref". Squad uses this to namespace your transactions.
  2. 424 on transfers — always call requery() before retrying. The transfer may have succeeded despite the timeout. Never retry with the same reference without checking first.
  3. Account lookup before transfer — always call accountLookup() first and pass its account_name result into initiateTransfer. Squad validates the name.
  4. Idempotent webhook handling — store processed transaction_ref values and check before fulfilling orders. Webhooks can be delivered more than once.
  5. BVN validation on virtual accounts — name, DOB, gender, and phone must exactly match the BVN record. Mismatches will be rejected.
  6. getAllTransactions date rangestart_date and end_date are required and the gap must not exceed one month.

Webhook IP Allowlist

Squad sends webhooks exclusively from:

18.133.63.109

Configure your firewall or server to accept inbound requests from this IP on your webhook endpoint path.


Contributing

Contributions are welcome. This section covers everything you need to get from zero to a merged PR.

Getting Started

git clone https://github.com/PADARLabs/@padar-labs/squad-ts.git
cd @padar-labs/squad-ts
npm install

Copy the example env file and add your sandbox credentials:

cp .env.example .env
# Edit .env and set SQUAD_SECRET_KEY=sandbox_sk_...

Development Commands

| Command | Description | |---------|-------------| | npm test | Run the full test suite | | npm run test:coverage | Run tests with coverage report | | npm run build | Compile to dist/ (ESM + CJS + types) | | npm run typecheck | Type-check without emitting | | npm run lint | Run ESLint | | npm run lint:fix | Auto-fix lint issues | | npm run format | Format with Prettier |

Project Structure

src/
  client.ts        # Base HTTP client (fetch-based)
  errors.ts        # SquadError class
  index.ts         # Public exports
  resources/       # One file per API resource
  types/           # TypeScript interfaces for all payloads and responses
tests/
  *.test.ts        # Jest tests per resource (fetch is mocked via jest.spyOn)

How to Add a New Endpoint

  1. Add the request/response types to the relevant file in src/types/
  2. Add the method to the resource class in src/resources/
  3. Export any new types from src/types/index.ts
  4. Add a test in tests/ using mockFetch() from tests/helpers.ts
  5. Add usage docs to README.md

Pull Request Guidelines

  • One PR per feature or fix — keep changes focused
  • Tests are required — all new methods need at least a success case and an error case
  • No new dependencies — the SDK is intentionally zero-dependency; raise an issue first if you think one is needed
  • Match existing code style — Prettier and ESLint configs are included; run npm run format && npm run lint:fix before pushing
  • Update the README — if you add or change a method, update the API Reference section

Reporting Issues

Please open an issue for:

  • Missing or undocumented API endpoints
  • Type errors or incorrect response shapes
  • Bugs or unexpected behaviour

Include the Squad API endpoint, the SDK method you called, and the error or unexpected response you received.


License

MIT © PADARLabs

See LICENSE for the full text.