Bonaventure OgetoBy Bonaventure Ogeto|

Paystack: The Complete Engineering Guide for African Developers

Paystack is a payment gateway that lets you accept card, bank transfer, mobile money, and USSD payments across African countries through a REST API. You initialize a transaction from your server, redirect the customer to Paystack checkout, then verify the payment server-side before granting value. Webhooks give you real-time confirmation.

What Paystack Is (and What It Is Not)

Paystack is a payment gateway for Africa. It sits between your application and the banks, card networks, and mobile money providers. You send Paystack an API request. Paystack collects the money from the customer. Paystack settles the money to your bank account. That is the whole job.

Paystack was founded in Lagos in 2015 and acquired by Stripe in 2020. It operates in Nigeria, Ghana, South Africa, and Kenya, with more countries in various stages of rollout. Each country has different payment channels available. In Nigeria, you get cards, bank transfers, USSD, and bank accounts. In Kenya, you get cards, M-Pesa, and Pesalink. The API is the same across countries, but the payment channels and settlement currencies change.

Here is what Paystack is not:

  • Not a bank. Paystack does not hold your money long-term. It collects payments and settles to your bank account on a schedule.
  • Not a mobile money provider. Paystack integrates with M-Pesa, but it is not M-Pesa. If you want direct STK Push control, you use the Daraja API directly.
  • Not a merchant of record. You are the merchant. Paystack processes payments on your behalf, but the tax obligations, refund policies, and customer relationships are yours.
  • Not a lending or credit platform. Paystack moves money. It does not extend credit to your customers.

The API is REST-based, uses JSON, and authenticates with secret keys. If you have ever called a REST API, you already know the transport layer. The payment-specific concepts (authorization, verification, webhooks, settlements) are what this guide covers.

For a deeper look at how Paystack processes a payment internally, read How Paystack Accept Payments Actually Works Under the Hood.

How Paystack Works: The Payment Flow

Every Paystack payment follows the same five steps. It does not matter whether the customer pays with a Visa card in Lagos, an M-Pesa account in Nairobi, or a bank transfer in Accra. The flow is identical from your server's perspective.

  1. Initialize. Your server calls POST /transaction/initialize with the amount (in the smallest currency unit), the customer's email, and an optional reference. Paystack responds with an authorization_url and an access_code.
  2. Redirect. You redirect the customer to the authorization_url. This opens Paystack's hosted checkout page, where the customer picks a payment channel and completes the payment. You never see their card number or M-Pesa PIN.
  3. Payment. The customer pays. Paystack talks to the card network, bank, or mobile money provider. This can take anywhere from two seconds (cards) to thirty seconds (mobile money) to a few minutes (bank transfers).
  4. Callback and Webhook. Two things happen roughly at the same time. Paystack redirects the customer back to your callback_url with a reference query parameter. Separately, Paystack sends a charge.success event to your webhook URL.
  5. Verify. Your server calls GET /transaction/verify/:reference and checks that the status is "success" and the amount matches what you expected. Only then do you grant value (deliver the product, activate the subscription, top up the wallet).

The verification step is not optional. A user can fabricate a redirect to your callback URL with a spoofed reference. If you grant value based only on the redirect, you will give away your product for free. Always verify server-side.

Here is the flow as a diagram you can keep in your head:

Your Server                    Paystack                   Customer
    |                              |                          |
    |-- POST /initialize --------->|                          |
    |<-- authorization_url --------|                          |
    |                              |                          |
    |        redirect customer to authorization_url           |
    |----------------------------------------------------->  |
    |                              |<--- customer pays -------|
    |                              |                          |
    |<-- webhook: charge.success --|                          |
    |        customer redirected to callback_url              |
    |<---------------------------------------------------------|
    |                              |                          |
    |-- GET /verify/:reference --->|                          |
    |<-- transaction details ------|                          |
    |                              |                          |
    | (grant value if verified)    |                          |

For the full breakdown of each step, including edge cases like abandoned checkouts and pending transactions, see Accepting Payments with Paystack: Complete Guide.

Accepting Your First Payment

This is the minimum code to initialize a Paystack transaction. It uses Node.js with the built-in fetch API (Node 18+). No SDK, no npm package. Just HTTP.

// initialize-transaction.js
// Requires: Node 18+ (for built-in fetch)
// Run: PAYSTACK_SECRET_KEY=sk_test_xxx node initialize-transaction.js

const PAYSTACK_SECRET_KEY = process.env.PAYSTACK_SECRET_KEY;

async function initializeTransaction(email, amountInKobo, reference) {
  const response = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${PAYSTACK_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email,
      amount: amountInKobo,
      reference,
      callback_url: 'https://yoursite.com/payment/callback',
    }),
  });

  const data = await response.json();

  if (!data.status) {
    throw new Error(`Paystack error: ${data.message}`);
  }

  return data.data;
  // { authorization_url, access_code, reference }
}

// Example: charge 5,000 Naira (amount is in kobo, so 5000 * 100)
initializeTransaction('customer@example.com', 500000, `ref_${Date.now()}`)
  .then((result) => {
    console.log('Redirect customer to:', result.authorization_url);
  })
  .catch(console.error);

Key points about this code:

  • Amount is in the smallest currency unit. For NGN, that is kobo (1 Naira = 100 kobo). For KES, it is cents (1 KES = 100 cents). For GHS, pesewas. So 5,000 Naira is 500000. 1,000 KES is 100000. Get this wrong and you will charge your customer 100x too much or too little. Read more in Understanding Kobo, Pesewa, and Cents.
  • The secret key goes in the Authorization header. This is your sk_test_ key (for testing) or sk_live_ key (for production). Never expose this key in frontend code. If you are wondering why, read Why You Must Never Call the Paystack API from Your Frontend.
  • The reference is optional but recommended. If you do not pass one, Paystack generates one for you. Generating your own gives you better control for reconciliation. See Transaction Reference Design Patterns.
  • The callback_url is where Paystack redirects the customer after payment. You can set a default in your Paystack dashboard and override it per transaction. See Callback URLs: Per-Transaction vs Dashboard.

After calling this endpoint, you get back an authorization_url. Redirect the customer there. Paystack handles the rest of the checkout UI. The customer picks their payment channel (card, bank transfer, USSD, M-Pesa), enters their details, and pays.

For an alternative approach where the checkout appears as a popup on your own page instead of a redirect, look at Paystack Popup InlineJS Explained Line by Line. For the difference between redirect and inline, see Redirect Checkout vs Inline Checkout.

Verifying Payments Server-Side

After the customer pays, Paystack redirects them to your callback_url with ?trxref=YOUR_REFERENCE&reference=YOUR_REFERENCE in the query string. Your job is to take that reference and verify the transaction with Paystack's API.

Here is the verification code:

// verify-transaction.js
// Requires: Node 18+
// Run: PAYSTACK_SECRET_KEY=sk_test_xxx node verify-transaction.js REF_HERE

const PAYSTACK_SECRET_KEY = process.env.PAYSTACK_SECRET_KEY;

async function verifyTransaction(reference) {
  const response = await fetch(
    `https://api.paystack.co/transaction/verify/${encodeURIComponent(reference)}`,
    {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${PAYSTACK_SECRET_KEY}`,
      },
    }
  );

  const data = await response.json();

  if (!data.status) {
    throw new Error(`Verification failed: ${data.message}`);
  }

  return data.data;
}

async function handleCallback(reference, expectedAmountInKobo) {
  const transaction = await verifyTransaction(reference);

  // Check 1: Was the payment successful?
  if (transaction.status !== 'success') {
    console.log('Payment was not successful. Status:', transaction.status);
    return false;
  }

  // Check 2: Does the amount match what we expected?
  if (transaction.amount !== expectedAmountInKobo) {
    console.log(
      'Amount mismatch. Expected:',
      expectedAmountInKobo,
      'Got:',
      transaction.amount
    );
    return false;
  }

  // Check 3: Is the currency correct?
  // (Important if you operate in multiple countries)
  if (transaction.currency !== 'NGN') {
    console.log('Unexpected currency:', transaction.currency);
    return false;
  }

  // All checks passed. Grant value.
  console.log('Payment verified. Customer email:', transaction.customer.email);
  return true;
}

// Example usage in an Express route:
// app.get('/payment/callback', async (req, res) => {
//   const { reference } = req.query;
//   const verified = await handleCallback(reference, 500000);
//   if (verified) {
//     res.redirect('/thank-you');
//   } else {
//     res.redirect('/payment-failed');
//   }
// });

Three things must be true before you grant value:

  1. transaction.status is "success" (not "abandoned", not "failed", not "pending")
  2. transaction.amount matches the amount you expected for that order
  3. transaction.currency is the currency you expected

Skipping the amount check is one of the most common payment integration bugs in Africa. An attacker can initialize their own transaction for 1 kobo, complete it, and pass your reference back. If you only check the status without verifying the amount, you grant full value for 1 kobo. For a thorough breakdown, see Verify Transaction: The Paystack Call You Must Never Skip and Verifying Amount and Currency Server-Side.

Also be aware of the double grant bug: your webhook and your callback handler can both fire for the same transaction. Make sure your grant-value logic is idempotent. The simplest approach is to check your database for an existing fulfilled order before granting again.

Webhooks: How Paystack Tells You What Happened

The callback redirect relies on the customer's browser. If they close the tab, lose connectivity, or their phone dies mid-redirect, you never get the callback. Webhooks are your safety net. Paystack sends a POST request to your webhook URL for every significant event, whether or not the customer's browser makes it back to your site.

The most important event is charge.success. It fires when a payment succeeds. For most applications, this is the only event you need to handle. See charge.success: The Only Event Most Apps Actually Need for a focused guide.

Here is how to handle a Paystack webhook with signature verification:

// webhook-handler.js
// Express.js webhook endpoint with signature verification
// IMPORTANT: You must read the raw body for signature verification.
// If you use express.json() globally, it will consume the body before
// you can hash it. Use express.raw() for the webhook route instead.

import crypto from 'node:crypto';
import express from 'express';

const app = express();

// Parse the webhook body as raw buffer, not JSON
app.post(
  '/webhooks/paystack',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const PAYSTACK_SECRET_KEY = process.env.PAYSTACK_SECRET_KEY;

    // Step 1: Verify the signature
    const hash = crypto
      .createHmac('sha512', PAYSTACK_SECRET_KEY)
      .update(req.body)
      .digest('hex');

    const signature = req.headers['x-paystack-signature'];

    if (hash !== signature) {
      console.error('Invalid webhook signature. Ignoring request.');
      return res.status(401).send('Invalid signature');
    }

    // Step 2: Respond with 200 immediately
    // Paystack will retry if you do not respond within a few seconds.
    res.status(200).send('OK');

    // Step 3: Parse and process the event
    const event = JSON.parse(req.body.toString());

    if (event.event === 'charge.success') {
      const transaction = event.data;
      console.log('Payment received:', transaction.reference);
      console.log('Amount:', transaction.amount, transaction.currency);

      // TODO: Verify the transaction via the API (belt and suspenders)
      // TODO: Update your database
      // TODO: Send confirmation email
    }
  }
);

app.listen(3000, () => console.log('Webhook server running on port 3000'));

Critical rules for webhook handlers:

  • Always verify the signature. The x-paystack-signature header contains an HMAC SHA-512 hash of the request body, using your secret key. If the hash does not match, someone is sending you a fake webhook. See Verifying the x-paystack-signature Header Correctly.
  • Return 200 immediately. Do your processing after responding. If your handler takes too long, Paystack will retry the webhook, and you might process the same event twice. See Why You Must Return 200 Immediately.
  • Use the raw body for signature verification. If your framework parses the JSON body before you hash it, the hash will not match because JSON serialization is not guaranteed to be byte-identical. This is the single most common webhook integration bug. Read The Raw Body Problem for framework-specific fixes.
  • Make your handler idempotent. Paystack can send the same event more than once (retries, network issues). Your handler should check whether it has already processed a given reference before doing anything. See Idempotent Webhook Handlers: The Complete Pattern.

For testing webhooks locally, you need a way to expose your local server to the internet. The two most common approaches are ngrok and Cloudflare Tunnel. For every event type Paystack sends and what to do with each, see Every Paystack Webhook Event Explained.

For the full engineering guide on webhooks, including queueing strategies, retry behavior, and deployment on serverless platforms, see Paystack Webhooks: Complete Engineering Guide.

Recurring Billing and Subscriptions

Paystack has a built-in subscriptions system. You do not need to build your own billing loop or store card tokens manually. The flow works like this:

  1. Create a Plan. A plan defines the billing interval (daily, weekly, monthly, quarterly, annually) and the amount. You can create plans via the API or the Paystack dashboard.
  2. Subscribe a Customer. When a customer pays for the first time, Paystack stores an authorization_code tied to their payment method. You use this authorization to create a subscription on a plan.
  3. Paystack Charges Automatically. On each billing cycle, Paystack charges the stored authorization and sends you a webhook event. If the charge fails (expired card, insufficient funds), Paystack retries according to its retry schedule.
  4. Customer Cancels or You Disable. Either the customer cancels through a Paystack-hosted link, or you disable the subscription via the API.

The authorization_code is central to recurring billing. When a customer pays you for the first time, Paystack returns an authorization object in the transaction data. This object contains a reusable authorization_code if the card supports it. For a detailed breakdown, see Authorization Codes: How Paystack Recurring Billing Really Works.

Common recurring billing patterns:

  • SaaS monthly billing. Create a plan for each pricing tier. Subscribe the customer after their first payment. See Building a SaaS Billing Page on Paystack.
  • Annual vs monthly. Create separate plans for each interval. Let the customer switch by canceling one subscription and creating another. See Annual vs Monthly Billing Logic.
  • Failed payment handling. When a recurring charge fails, Paystack sends invoice.payment_failed events. You should email the customer and give them a window to update their payment method before disabling their access. See Building a Dunning Email Sequence.

For subscription-related webhook events (subscription.create, subscription.disable, invoice.create, invoice.update), see Handling Subscription and Invoice Events.

Transfers and Payouts

Transfers let you send money from your Paystack balance to bank accounts or mobile money wallets. This is how you pay vendors, refund customers outside the normal refund flow, pay drivers, or distribute earnings to creators.

The transfer flow has three steps:

  1. Create a Transfer Recipient. You register the destination bank account (account number, bank code, name) or mobile money wallet. Paystack returns a recipient_code.
  2. Initiate a Transfer. You call the Transfer endpoint with the recipient_code, the amount, and a reason. Depending on your account settings, the transfer either processes immediately or waits for approval in the Paystack dashboard.
  3. Confirm via Webhook. Paystack sends transfer.success, transfer.failed, or transfer.reversed events to your webhook URL. See Handling Transfer Events for the full breakdown.

Important details for transfers:

  • Transfers come from your Paystack balance, not your bank account. You can only transfer money that customers have already paid you and that has settled.
  • OTP or dashboard approval may be required. Paystack can require OTP verification or manual dashboard approval for transfers, depending on your settings and transfer size. Automated systems should use the API-based approval flow.
  • In Kenya, you can transfer to M-Pesa wallets, bank accounts, and Paybill/Till numbers. See Transfers to M-Pesa Wallets and Transfers to Paybill and Till Numbers.

For bulk payouts (paying many recipients at once), see Bulk Transfers with the Paystack API. For building a complete payout system, see Building an Automated Payout System on Paystack.

Split Payments and Marketplaces

If you are building a marketplace, a multi-vendor store, or any platform where money flows to multiple parties per transaction, Paystack split payments handle the distribution for you.

Splits work at the transaction level. When you initialize a transaction, you can specify how the payment should be divided:

  • Percentage split. Platform takes 10%, vendor gets 90%.
  • Flat split. Platform takes a fixed fee per transaction, vendor gets the rest.
  • Multi-party split. Distribute a single payment across three or more subaccounts.

Each vendor or service provider gets a Paystack subaccount. You create subaccounts via the API, specifying the vendor's bank account details and their settlement schedule. When a customer pays, Paystack splits the money automatically and settles each party's share separately.

This removes a significant compliance and operational burden. Without splits, you would collect all the money, hold it, and manually transfer each vendor's share. With splits, Paystack settles directly to each party. You never hold the vendor's money.

Example use cases:

For the full implementation pattern, see Building a Multi-Vendor Store with Paystack.

Paystack in Kenya: M-Pesa, Pesalink, and Cards

Kenya is a different payment market from Nigeria or Ghana. Mobile money dominates. Cash is common for in-person transactions. Card penetration is low compared to West Africa. When you build on Paystack in Kenya, the engineering decisions change accordingly.

What Paystack Supports in Kenya

As of the last update, Paystack in Kenya supports:

  • Cards (Visa, Mastercard, Verve)
  • M-Pesa (through Paystack's checkout flow)
  • Pesalink (bank-to-bank instant transfers)
  • Apple Pay (for merchants who complete domain verification)

For the full rundown of what works and what does not, start with Paystack in Kenya: What It Supports and What It Does Not.

M-Pesa Through Paystack

This is the question every Kenyan developer asks: "Can I do STK Push with Paystack?" The answer is nuanced. Paystack collects M-Pesa payments, but the experience is different from a direct Daraja STK Push.

With direct Daraja, you send an STK Push and the customer sees a prompt on their phone immediately. With Paystack, the customer goes through the Paystack checkout page, selects M-Pesa as their payment method, enters their phone number, and then receives the push. It is one extra step.

The tradeoff: Paystack gives you a unified API for cards and M-Pesa, handles reconciliation and settlements, and removes the complexity of managing Daraja credentials and callbacks. Daraja gives you a more direct M-Pesa experience and lower per-transaction fees, but you manage the entire integration yourself.

For the full engineering comparison, read Paystack vs Daraja: When to Use a Gateway and When to Go Direct. For the side-by-side comparison of user experience and technical tradeoffs, see M-Pesa Through Paystack vs Direct Daraja STK Push.

If you decide to use Paystack for M-Pesa collection, see:

If you decide to go with direct Daraja instead, Migrating from Paystack to Direct Daraja covers the switch. The reverse path is covered in Migrating from Direct Daraja to Paystack.

Some teams run both. See Running Paystack and Daraja Side by Side for the architecture pattern.

Pesalink

Pesalink is a bank-to-bank instant transfer network in Kenya. Paystack supports it as a payment channel, meaning customers can pay by transferring directly from their bank account. For the developer guide, see Pay with Pesalink on Paystack.

Settlements in Kenya

Paystack settles to Kenyan bank accounts and M-Pesa wallets. Settlement timelines vary by payment channel and your account configuration. For details, see Kenya Settlement to Bank or M-Pesa Wallet and Settlement Timelines and What Engineers Should Assume.

Checkout Conversion in Kenya

Where you place M-Pesa in your checkout flow affects your conversion rate. If cards appear first and M-Pesa is buried, you lose Kenyan customers. This is a design decision with engineering implications. See Why Kenyan Checkout Conversion Depends on Mobile Money Placement.

Kenya-Specific Build Guides

These guides use Kenyan payment channels and business contexts:

Compliance in Kenya

Kenya has specific data protection and tax requirements that affect payment integrations. The Kenya Data Protection Act governs how you store and process customer payment data. Digital Service Tax applies to certain online services. For engineering implications, see Kenya Data Protection Act and Payment Data Storage and Kenya Digital Service Tax and Online Payment Products.

KRA's eTIMS system for electronic tax invoices may also interact with your payment receipt flow. See KRA eTIMS and Payment Receipt Integration.

Security and PCI Compliance

When you use Paystack's hosted checkout (redirect or Inline JS), Paystack handles PCI compliance. Card numbers go directly to Paystack's servers. They never touch your backend. You do not need to fill out a PCI Self-Assessment Questionnaire for SAQ-A or become PCI certified yourself, as long as you use the hosted checkout.

If you use the Charge API to build a fully custom checkout, the PCI requirements change. You are collecting card details on your own page and sending them to Paystack. This puts you in SAQ-A-EP territory at minimum. For most African startups, the hosted checkout is the right choice. Save the custom checkout for when you have a compliance team and a specific UX requirement that the hosted checkout cannot satisfy.

Security practices you should follow regardless of checkout type:

  • Keep your secret key secret. It goes in environment variables on your server, never in frontend code, never in a Git repository, never in a Slack message. Rotate it immediately if it is ever exposed.
  • Verify webhook signatures. Every webhook request from Paystack includes an HMAC-SHA512 signature. Check it. See Fake Webhook Attacks and How Signature Checks Stop Them.
  • Verify transactions server-side. Do not rely solely on the client-side redirect to confirm a payment.
  • Use HTTPS everywhere. Your callback URL and webhook URL must be HTTPS. Paystack will not send webhooks to HTTP endpoints in production.
  • Log carefully. Do not log full card numbers, CVVs, or PINs. Do not log your secret key. Log transaction references, amounts, and statuses. See Logging Payments Without Logging Secrets.
  • Monitor for fraud signals. Paystack provides fraud-related data in transaction responses. See Fraud Signals in Transaction Data.

For IP allowlisting on your webhook endpoint, see Webhook IP Allowlisting.

Testing Your Integration

Paystack provides a full test environment. Use your sk_test_ and pk_test_ keys. No real money moves. No real cards are charged. Test keys and live keys are separate; you cannot accidentally mix them if you use environment variables properly.

Test mode details:

  • Test cards. Paystack provides specific test card numbers in their documentation. These cards simulate successful payments, failed payments, and various error scenarios.
  • Test bank transfers. In test mode, bank transfers are auto-confirmed after a few seconds without any actual bank involvement.
  • Test webhooks. Paystack sends webhooks in test mode, but you still need a publicly accessible URL for your webhook endpoint. Use ngrok or Cloudflare Tunnel during development.
  • Test mobile money. In Kenya, you can simulate M-Pesa payments in test mode without involving a real M-Pesa account.

For automated testing in your CI pipeline, see Testing Webhooks in CI Without a Public URL and Simulating Webhook Events for Automated Tests.

For end-to-end testing with browser automation, see End-to-End Payment Tests with Playwright and End-to-End Payment Tests with Cypress.

For testing failure paths (what happens when a payment fails, when a webhook times out, when the network drops mid-checkout), see Chaos Testing Payment Failure Paths.

A common testing mistake: forgetting to test the unhappy paths. Your integration works when payments succeed. But what happens when a payment fails? When the customer abandons checkout? When your webhook handler throws an error? When the amount is wrong? Test all of these before going live. See CI Pipelines for Payment Code for a practical setup.

Errors You Will Hit (and How to Fix Them)

These are the errors that show up in almost every Paystack integration. You will hit at least three of them.

401 Unauthorized

Your secret key is wrong, missing, or you are using a test key against the live API (or vice versa). Check your Authorization header. Make sure it says Bearer sk_test_xxx or Bearer sk_live_xxx. Not Basic. Not Token. Not the public key. See Paystack 401 Unauthorized on Transaction Initialize.

Amount Too Low

Each currency has a minimum transaction amount. If you try to charge below the minimum, Paystack rejects the request. Remember that amounts are in the smallest currency unit (kobo, pesewas, cents). See Paystack Amount Too Low Error.

Webhook Signature Mismatch

Your signature check fails because your framework parsed the JSON body before you could hash the raw bytes. This is the single most common webhook bug across all languages and frameworks. See The Raw Body Problem.

Callback URL Not Working

Your callback URL is not accessible from the internet, is using HTTP instead of HTTPS, or is returning an error. In development, make sure you are using a tunnel (ngrok, Cloudflare Tunnel) and that the URL is correctly configured in your dashboard or the API request. See Callback URLs: Per-Transaction vs Dashboard.

Duplicate Charges

Your webhook handler and callback handler both grant value for the same transaction. Or your webhook handler processes the same event twice because Paystack retried. Make your handlers idempotent. See Idempotent Webhook Handlers and The Double Grant Bug.

Pending Transactions

Some payment channels (bank transfers, USSD) can leave transactions in a pending state for minutes or even hours. Your code must handle this state. Do not treat pending as success. Do not treat pending as failure. Poll the verify endpoint or wait for the webhook. See Pending Transactions: Handling the Grey Zone.

For a comprehensive error reference, including every gateway response code and what it means, see Paystack Gateway Responses. For debugging tools, see Debugging with the Transaction Timeline and Debugging with the Paystack CLI.

Building Real Products on Paystack

Everything above covers the API mechanics. The real work is turning those mechanics into products people pay for. The patterns change depending on what you are building.

E-commerce checkout. You need to initialize transactions, verify payments, handle abandoned carts, and issue refunds. If you sell physical goods, you also need to tie payment confirmation to inventory and shipping. See Kenyan E-Commerce Checkout.

SaaS subscription. You need plans, subscriptions, dunning (handling failed recurring charges), and plan upgrades/downgrades. See Building a SaaS Billing Page and Building a Membership Site.

Marketplace with payouts. You need split payments (subaccounts) for real-time splitting, or transfers for batch payouts. You need to handle vendor onboarding, settlement reporting, and disputes. See Building a Two-Sided Marketplace.

Wallet or prepaid system. You need to accept deposits (normal Paystack transactions), track balances in your database, and handle withdrawals (transfers). See Building a Wallet Top-Up Flow.

Invoicing tool. Paystack has a Payment Requests API that creates invoices with payment links. See Payment Requests and Invoices via API. For a Kenya-specific invoicing tool, see Freelancer Invoicing Tool for Kenyan Developers.

The builds hub has complete project guides that walk you through architecture, database design, API integration, and deployment for specific product types. Browse them at the builds section of the Paystack content cluster.

Getting Paid to Build Paystack Integrations

Payment integration is a high-demand skill in African tech. Every business that sells online needs it. Most developers avoid it because payment code feels risky. That gap is an opportunity.

Ways to earn from Paystack integration skills:

To build a portfolio that demonstrates payment integration skills, see Building a Payments Portfolio That Gets You Hired.

Paystack with Your Stack

The code examples in this guide use plain Node.js with fetch. Paystack's API is language-agnostic, so you can use it from any backend that makes HTTP requests. The hub has framework-specific integration guides:

For webhook handling in specific frameworks, the webhook hub covers the common body-parsing and CSRF issues:

For deploying webhook handlers on serverless platforms:

Where to Go From Here

This guide covers the full surface area of Paystack at a working level. Each section links to deeper guides on specific topics. Here is a summary of the content hubs in the Paystack cluster, organized by what you are trying to do:

Join the McTaba newsletter. Practical engineering content on payments, M-Pesa, USSD, WhatsApp, and AI for African developers.

Key Takeaways

  • ✓Paystack is a REST API. You initialize transactions from your server, Paystack handles checkout, and you verify the result server-side before granting value.
  • ✓Never trust the frontend. Always verify a transaction by calling the Paystack Verify endpoint from your server and checking both the status and the amount.
  • ✓Webhooks are the most reliable confirmation channel. Your webhook handler must verify the signature, respond with 200 immediately, then process the event asynchronously.
  • ✓In Kenya, Paystack supports M-Pesa, Pesalink, and cards. M-Pesa goes through Paystack checkout, not a direct STK Push, which changes the user experience.
  • ✓Use test keys for development. Paystack gives you separate public and secret keys for test and live environments. Never mix them.
  • ✓Paystack handles PCI compliance for you when you use their hosted checkout or Inline JS. You do not need to store or touch raw card numbers.
  • ✓Split payments, subscriptions, and transfers are all built into the same API. You do not need a separate provider for payouts or marketplace billing.

Frequently Asked Questions

Is Paystack free to use?
Creating a Paystack account is free. There are no setup fees, no monthly fees, and no minimum commitments. Paystack charges a per-transaction fee when you successfully collect a payment. The fee structure varies by country and payment channel. Check paystack.com/pricing for the current rates in your country, because these numbers change and differ between local and international transactions.
What countries does Paystack support?
Paystack operates in Nigeria, Ghana, South Africa, and Kenya. Each country has different payment channels available. Nigeria has the widest range (cards, bank transfers, USSD, bank accounts). Kenya supports cards, M-Pesa, and Pesalink. Paystack occasionally announces expansion to new countries, so check their website for the latest list.
Can I use Paystack with M-Pesa in Kenya?
Yes. Paystack supports M-Pesa as a payment channel in Kenya. Customers select M-Pesa on the Paystack checkout page, enter their phone number, and receive an STK Push prompt. The experience is slightly different from a direct Daraja integration because the customer goes through Paystack checkout first. The tradeoff is that Paystack gives you a single API for both cards and M-Pesa, and handles settlements and reconciliation.
Do I need a registered business to use Paystack?
Paystack requires business verification to activate your account for live transactions. The specific documents needed depend on your country. In Nigeria, you typically need a CAC registration. In Kenya, you need a business registration certificate or KRA PIN certificate. You can start building and testing with test keys immediately, without any business documents. The verification is only needed when you want to accept real payments.
How long does a Paystack integration take?
A basic integration (accept a payment, verify it, handle the webhook) can be done in a day if you have backend experience. A production-grade integration with proper error handling, reconciliation, retry logic, and testing takes one to two weeks. Complex setups like marketplace splits or subscription billing with dunning add more time. The API itself is straightforward. Most of the time goes into handling edge cases and failure scenarios.
What programming languages work with Paystack?
Any language that can make HTTP requests works with Paystack. The API is a standard REST API that accepts JSON. Paystack provides official libraries for Node.js, PHP, and a few other languages, but you do not need them. A simple fetch or HTTP client call is enough. This guide uses Node.js with fetch, but the same API calls work in Python (requests), Go (net/http), Ruby (Net::HTTP), Java (HttpClient), C# (HttpClient), and any other language.
Is Paystack PCI compliant?
Yes. Paystack is PCI DSS Level 1 certified, which is the highest level of PCI compliance. When you use Paystack hosted checkout (redirect or Inline JS), card data goes directly to Paystack servers and never touches your backend. This means you do not need your own PCI certification for SAQ-A eligibility. If you use the Charge API to build a custom checkout where your server handles card details, your PCI obligations increase.
How does Paystack compare to Flutterwave?
Both are payment gateways serving African markets. They cover similar countries and payment channels. The differences are in API design, documentation quality, settlement reliability, specific feature availability, and pricing. The right choice depends on your country, payment channels, and specific feature needs. For a detailed engineering comparison, see the Paystack vs Flutterwave article in the comparison hub.
How do Paystack settlements work?
Paystack collects payments from your customers and settles the funds to your bank account (or M-Pesa wallet in Kenya) on a regular schedule. Settlement timelines vary by country and payment channel. The settlement amount is the transaction amount minus Paystack fees. You can check your settlement schedule and history in the Paystack dashboard. For engineering considerations around settlements, see the settlement timelines article in the accepting payments hub.
Can I use Paystack for both collecting payments and sending payouts?
Yes. Paystack supports both directions. You collect payments through the Transaction API (initialize, checkout, verify). You send payouts through the Transfer API (create transfer recipient, initiate transfer). Payouts come from your Paystack balance, meaning you can only transfer money that customers have already paid you. Both directions use the same API keys and the same webhook infrastructure.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon