Bonaventure OgetoBy Bonaventure Ogeto|

How Paystack Accept Payments Actually Works Under the Hood

When you initialize a Paystack transaction, your server sends the amount and customer email to Paystack. Paystack creates a checkout session and returns an authorization URL. The customer enters payment details on that page. Paystack tokenizes the card, sends an authorization request through the card network (Visa/Mastercard) to the issuing bank, receives approval or decline, captures the funds, and eventually settles the money into your bank account minus fees.

The Six Players in Every Payment

Before tracing the flow, you need to know who is involved. A single Paystack card payment passes through at least six distinct systems, and each one has a job to do.

1. Your application (the merchant server)

This is your Node.js, Django, Laravel, or Rails backend. It initiates the transaction by calling Paystack's API with the amount, customer email, and a reference. After payment, it verifies the result and grants value (delivers the product, credits the wallet, activates the subscription).

2. Paystack (the payment processor)

Paystack sits between you and the banking system. It provides the checkout UI, tokenizes card data, routes the transaction to the correct card network, handles 3DS verification, manages retries, sends webhooks, and eventually settles funds into your bank account.

3. The card network (Visa, Mastercard, Verve)

The card network is the communication highway. When Paystack sends an authorization request, it goes through the card network to reach the customer's bank. Visa and Mastercard are international networks. Verve is a Nigerian domestic network. The card network does not hold money. It routes messages and enforces rules.

4. The issuing bank (customer's bank)

This is the bank that gave the customer their card. GTBank, Access Bank, FirstBank, Stanbic, Standard Chartered, or whoever. The issuing bank decides whether to approve or decline the transaction. It checks the customer's balance, fraud rules, card status, and spending limits.

5. The acquiring bank (Paystack's bank partner)

Paystack works with acquiring banks to process transactions and receive funds from the card networks. The acquiring bank receives the captured funds from the issuing bank (through the card network) and passes them to Paystack for settlement to you.

6. The customer

The person paying. They see a checkout page, type in card details (or pick another payment method), and approve the charge. Everything that happens between their button click and your webhook is invisible to them.

Step-by-Step: The Full Payment Flow

Here is the complete sequence for a card payment using Initialize Transaction. Each step happens in order, and each depends on the previous one succeeding.

Text sequence diagram:

Customer  -->  Your Server  -->  Paystack API  -->  Paystack Checkout
                                                         |
                                                    Customer enters
                                                    card details
                                                         |
                                                    Paystack tokenizes
                                                         |
                                              Paystack  -->  Card Network  -->  Issuing Bank
                                                                                   |
                                                                             Approve / Decline
                                                                                   |
                                              Paystack  <--  Card Network  <--  Issuing Bank
                                                   |
                                              Capture funds
                                                   |
                                         Send webhook (charge.success)
                                                   |
                                         Redirect customer to callback_url
                                                   |
Your Server verifies transaction  <--  Paystack verify endpoint
                                                   |
                                         (Later) Settlement to your bank

Now let us walk through each phase in detail.

Phase 1: Initialize (your server to Paystack)

// Your server sends this request
const response = await fetch('https://api.paystack.co/transaction/initialize', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'customer@example.com',
    amount: 500000, // 5,000 NGN in kobo
    reference: 'order_142_' + Date.now(),
    callback_url: 'https://yoursite.com/payment/callback',
  }),
});

const data = await response.json();
// data.data.authorization_url = "https://checkout.paystack.com/abc123xyz"
// data.data.access_code = "abc123xyz"
// data.data.reference = "order_142_1721472000000"

Paystack validates your request (checks your secret key, verifies the amount is above the minimum, checks that the email is valid), creates a transaction record in their database, and returns three values: the authorization URL, access code, and reference. At this point, no money has moved. No bank has been contacted. The transaction exists only as a pending record in Paystack's system.

Phase 2: Checkout (customer on Paystack's page)

You redirect the customer to the authorization URL or open the checkout as a popup using the access code. Paystack's checkout page loads. The customer sees the amount, picks a payment method, and enters their details. For a card payment, they type in the card number, expiry date, and CVV.

Phase 3: Tokenization (Paystack's servers)

The card details go from the checkout page directly to Paystack's PCI-compliant servers over HTTPS. Paystack replaces the raw card number with a token. From this point on, the actual card number is never stored or transmitted in the clear. Paystack uses the token internally to communicate with the card network.

Phase 4: Authorization (Paystack to card network to issuing bank)

Paystack formats an authorization request and sends it through the card network (Visa or Mastercard or Verve) to the customer's issuing bank. The request says: "This customer wants to pay 5,000 NGN to this merchant. Approve or decline."

The issuing bank runs its checks: Does the customer have enough balance? Is the card active? Is this transaction flagged by fraud rules? Is the customer within their daily spending limit? The bank sends back an approval code or a decline reason.

Phase 5: 3DS verification (if required)

For many African card transactions, the bank requires 3D Secure verification. The customer gets redirected to their bank's verification page or receives an OTP via SMS. They enter the OTP on the bank's page. The bank confirms the OTP and sends a final approval back through the card network to Paystack. This is why some transactions have that extra redirect step after entering card details.

Capture, Webhooks, and Redirect

Phase 6: Capture

After the issuing bank approves and 3DS (if required) passes, Paystack captures the funds. Capture means the bank locks that amount on the customer's account. The customer sees the debit on their statement. The money is now in transit from the issuing bank through the card network to the acquiring bank.

For standard Initialize Transaction flows, authorization and capture happen back-to-back within seconds. The customer clicks "pay," and within 5 to 30 seconds, the charge is authorized, 3DS is completed, and the funds are captured. For preauthorization flows (hold-and-capture), these are separate steps you control.

Phase 7: Webhook delivery

Immediately after capture, Paystack fires a charge.success webhook to your configured webhook URL. The webhook payload contains the full transaction object: amount, currency, reference, customer info, authorization details, metadata, and the gateway response.

// What Paystack sends to your webhook URL
// POST https://yoursite.com/api/webhooks/paystack
{
  "event": "charge.success",
  "data": {
    "id": 12345678,
    "reference": "order_142_1721472000000",
    "amount": 500000,
    "currency": "NGN",
    "status": "success",
    "customer": {
      "email": "customer@example.com"
    },
    "authorization": {
      "authorization_code": "AUTH_abc123",
      "card_type": "visa",
      "last4": "4081",
      "bank": "GTBank"
    },
    "metadata": {}
  }
}

The webhook arrives independently of the customer redirect. Sometimes the webhook reaches your server before the customer is redirected back. Sometimes the customer arrives at your callback URL before the webhook fires. Your code must handle both orderings. The standard approach is to use a database flag: whichever arrives first marks the order as paid; whichever arrives second checks the flag and skips.

Phase 8: Customer redirect

After the checkout completes, Paystack redirects the customer to your callback URL with the reference as a query parameter. Your callback handler extracts the reference, calls the verify endpoint, and shows the customer a success or failure page.

The redirect is a convenience for the customer's experience. It is not proof of payment. Never grant value based solely on a customer landing on your callback URL. Always verify server-side.

Settlement: When the Money Actually Arrives

Settlement is the step most developers forget about, and it is the one your finance team cares about most. After capture, the money does not land in your bank account immediately. Here is what happens.

After the acquiring bank receives the funds from the card network, it passes them to Paystack. Paystack deducts their processing fees and holds the remainder in your Paystack balance. This balance is visible on your dashboard as "Available balance."

On each settlement cycle, Paystack transfers your available balance to your bank account. The settlement schedule depends on your country and account configuration:

  • Nigeria (NGN): Typically next business day (T+1). A payment captured on Monday is settled on Tuesday. A payment captured on Friday is settled on Monday.
  • Ghana (GHS): Settlement timelines vary. Check your Paystack dashboard for your specific schedule.
  • South Africa (ZAR): Settlement timelines vary by account type.
  • Kenya (KES): Settlement timelines vary by account type.

The distinction between "payment successful" and "settled to your bank" is important. Your application should grant value after the payment is verified (Phase 8), not after settlement. If you wait for settlement before delivering a product, your customers will wait days. But your accounting system should track both events separately: "Customer paid" and "Paystack settled."

You can query your settlement status through the Paystack API:

// Check your settlements
const response = await fetch('https://api.paystack.co/settlement', {
  headers: {
    Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
  },
});

const settlements = await response.json();
// Each settlement contains the total amount, the settlement date,
// and the list of transactions included

For a deeper look at settlement mechanics, see settlement timelines and what engineers should assume.

How Non-Card Channels Differ

The flow above describes a card payment. Other payment channels follow a similar structure but skip the card network entirely.

Bank transfer

Paystack generates a temporary bank account number. The customer sends money from their banking app to that account. The receiving bank notifies Paystack when the transfer lands. Paystack matches the deposit amount to the pending transaction and marks it as successful. No card network, no tokenization, no 3DS. The bottleneck is how quickly the customer's bank processes the transfer, which can take anywhere from seconds to a few minutes.

USSD

Paystack generates a USSD code. The customer dials it on their phone. Their bank's USSD service handles the authorization (usually a PIN prompt on the phone). The bank notifies Paystack of the authorization result. No internet required on the customer's end. The latency depends on USSD session processing at the bank, which varies widely.

Mobile money (Ghana)

Paystack sends a charge request to the mobile money provider (MTN, Vodafone, AirtelTigo). The provider sends an authorization prompt to the customer's phone. The customer approves on their device. The provider notifies Paystack. Mobile money bypasses the traditional banking system entirely. Funds move within the mobile money network.

The common thread

Regardless of channel, the beginning and end of the flow are the same from your perspective. You initialize a transaction on your server. The customer pays through some mechanism. Paystack sends you a webhook. You verify and grant value. The middle steps (how the customer authorizes the payment) change, but your code does not.

This is one of the advantages of using Initialize Transaction. Paystack handles channel-specific logic in their checkout UI. You write one integration, and the customer picks the channel that works for them.

Where Things Fail and How to Diagnose

Payments fail at specific points in the chain. Knowing where a failure happened tells you what to do about it.

Failure at initialization

Your request to /transaction/initialize returns a non-200 response. Causes: invalid secret key, amount below minimum, malformed email, missing required fields, or Paystack API downtime. Fix: check the error message in the response body. These are always your integration bugs or configuration issues.

Failure at the card network

The card network cannot route the transaction. Causes: invalid card number (BIN not recognized), network timeout, or the card brand is not supported in the merchant's country. The gateway response usually says something like "Card not supported" or "Unable to route transaction."

Failure at the issuing bank

The bank declines the authorization. Causes: insufficient funds, card expired, card blocked, spending limit exceeded, fraud flag, or bank system downtime. The gateway response tells you the decline reason. "Insufficient funds" is the most common. "Do not honor" is the bank's catch-all for "we are not telling you why."

Failure at 3DS

The customer does not complete 3D Secure verification. Causes: OTP expired, customer entered wrong OTP three times, bank's verification page timed out, or the customer closed the browser during the OTP step. The transaction stays in a "failed" or "abandoned" state.

Failure at webhook delivery

The payment succeeded but your server did not process the webhook. Causes: your webhook endpoint returned a non-200 status, your server was down, or your endpoint timed out. Paystack retries webhooks on failure, but if your server is consistently down, you miss notifications. This is why you should always verify transactions in your callback handler as a fallback.

For every failed transaction, check the gateway_response field in the transaction object. It contains the reason string from the bank or network. For a full catalog of gateway responses and what each one means, see reading gateway responses.

Authorization Codes and Reusable Tokens

When a card payment succeeds, the verify response includes an authorization object. This object contains an authorization_code (like AUTH_abc123def456) that represents the customer's card. You can use this code to charge the same card again without the customer re-entering their details.

// After verifying a successful transaction, extract the authorization
const verifyResponse = await fetch(
  'https://api.paystack.co/transaction/verify/' + reference,
  {
    headers: {
      Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
    },
  }
);

const data = await verifyResponse.json();

if (data.data.status === 'success') {
  const auth = data.data.authorization;
  // auth.authorization_code = "AUTH_abc123def456"
  // auth.reusable = true (or false)
  // auth.card_type = "visa"
  // auth.last4 = "4081"
  // auth.bank = "GTBank"

  // Store auth.authorization_code in your database
  // linked to this customer, ONLY if auth.reusable is true
}

The reusable field is critical. Not all authorization codes are reusable. Some banks or card types do not support recurring charges. Check auth.reusable === true before storing the code for future use.

When you charge a reusable authorization code, the payment skips the checkout page entirely. The customer does not see any UI. The charge happens server-to-server. This is how subscription renewals, wallet auto-top-ups, and saved-card payments work.

Authorization codes are tied to a specific card and a specific Paystack merchant account. They do not expire on a fixed schedule, but they become invalid if the customer's card expires, is replaced, or is blocked by the bank. Always handle authorization failures gracefully and prompt the customer to re-enter their card details when a stored authorization fails.

Where Fees Get Deducted

Paystack deducts processing fees before settling funds to your bank account. The customer pays the full amount you specified. You receive the amount minus Paystack's fees.

The deduction happens during settlement, not during capture. When a customer pays 5,000 NGN, Paystack captures the full 5,000 NGN. The transaction amount in your dashboard, webhooks, and API responses is always the full amount. When Paystack settles the transaction to your bank, they deduct their fee and transfer the remainder.

Your settlement report shows the breakdown:

// Settlement detail (simplified)
{
  "total_amount": 500000,      // Total collected (5,000 NGN in kobo)
  "total_fees": 7500,          // Paystack fees deducted (in kobo)
  "net_amount": 492500,        // What lands in your bank (in kobo)
  "settlement_date": "2026-07-21"
}

Fee structures vary by country, transaction volume, and the agreement you have with Paystack. Check your Paystack dashboard for your specific rate. Do not hardcode fee calculations into your application. Instead, use the settlement reports from the API to reconcile what was collected versus what was settled.

One common mistake: developers calculate the expected fee client-side and show the customer "You pay X, we receive Y." This breaks when Paystack adjusts fees, when fee caps apply, or when the fee structure changes for different payment channels. Bank transfers, cards, and mobile money often have different fee structures. Show the customer the amount they pay. Track what you receive through settlements.

Putting It All Together: The Mental Model

Here is the mental model to carry with you when building on Paystack:

Your code touches three moments:

  1. Initialize: You tell Paystack how much and who. Paystack gives you a checkout URL.
  2. Verify: After the customer pays (or does not), you ask Paystack what happened. You check the status, amount, and currency.
  3. Fulfill: If verification passes, you deliver the value. You mark the order as paid in your database.

Everything between initialize and verify is handled by Paystack. The checkout page, the card tokenization, the bank communication, the 3DS verification, the capture. You do not write code for any of that. You do not even see it happening. Your server sends a request, waits for the webhook or redirect, and then verifies.

Two notification channels, same result

After payment, you get notified two ways: the webhook (server-to-server, automatic) and the redirect callback (customer's browser hitting your URL). Both carry the reference. Both should trigger verification. Both should be idempotent. If you handle both correctly, you never miss a payment and you never double-grant value.

The timeline

From the customer's perspective, payment takes seconds. From your application's perspective, you get a webhook within seconds of payment. From your finance team's perspective, the money arrives in your bank account on the next settlement cycle (often the next business day). These three timelines are different, and your application should respect each one.

The full lifecycle, from the customer clicking "Pay" to the money hitting your bank account, involves six parties, at least four network hops, and anywhere from a few seconds to a few days. But your code only needs to handle three moments. That is the value of a payment processor like Paystack: it absorbs the complexity so your integration stays simple.

For the broader picture of how accept payments fits into the full Paystack engineering workflow, see the complete accept payments guide.

Stay Up to Date

Paystack regularly updates their API, adds new payment channels, and changes how settlement works. We keep these guides current when things change.

Join the McTaba newsletter to get notified when we publish new Paystack engineering guides or spot breaking changes you should know about.

Sign up for the McTaba newsletter

Key Takeaways

  • A Paystack payment involves at least six parties: your server, Paystack, the card network (Visa/Mastercard), the issuing bank, the acquiring bank, and the customer. Each party adds latency and potential failure points.
  • Tokenization happens on Paystack servers. Card numbers never touch your application. Paystack replaces raw card data with a token before talking to the bank.
  • Authorization and capture are two separate steps. The bank first approves (authorizes) the charge, then Paystack captures the funds. For standard payments, both happen within seconds. For preauthorization flows, capture happens later.
  • Settlement is not instant. The money sits with Paystack after capture and gets transferred to your bank account on a settlement cycle (typically T+1 in Nigeria). Your available balance and settled balance are different numbers.
  • Webhook delivery happens after successful authorization and capture, not after settlement. You get notified that the customer paid before the money lands in your bank account.
  • Failed payments can fail at multiple points: Paystack validation, card network routing, issuing bank authorization, or 3DS verification. The gateway_response field tells you where and why.

Frequently Asked Questions

How long does a Paystack card payment take from start to finish?
From the customer entering their card details to the webhook arriving on your server, a typical card payment takes 5 to 30 seconds. The variation depends on the customer's bank response time, whether 3DS verification is required, and network latency. Settlement to your bank account happens separately, usually the next business day in Nigeria.
Does my server ever see the customer's card number?
No, not if you use Initialize Transaction or the Paystack Inline JS popup. Card details go directly from the Paystack checkout page to Paystack's PCI-compliant servers. Your server only sees tokens, authorization codes, and the last four digits. If you use the Charge API to build a fully custom checkout, you may handle card details, which comes with PCI compliance responsibilities.
What is the difference between authorization and capture in Paystack?
Authorization is the bank approving the charge. Capture is Paystack locking the funds on the customer's account. For standard payments through Initialize Transaction, both happen automatically within seconds. For preauthorization flows, you authorize first (placing a hold on the funds) and capture later when you are ready to finalize the charge.
Why did the customer get charged but my webhook did not arrive?
The payment succeeded on Paystack's end, but the webhook delivery to your server failed. This happens when your webhook endpoint returns a non-200 status code, times out, or your server is unreachable. Paystack retries failed webhooks, but you should always verify transactions in your redirect callback handler as a backup. Check the webhook logs in your Paystack dashboard to see delivery attempts and error codes.
Can I see which step a failed payment failed at?
Yes. The transaction object returned by the verify endpoint includes a gateway_response field that tells you why the payment failed. Common values include "Insufficient Funds," "Card Expired," "Transaction Declined," and "Do not honour." For more granular debugging, the transaction timeline API shows every step the transaction went through and where it stopped.

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