McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Reconciling Payouts Against Recipient Confirmations
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Reconciling Payouts Against Recipient Confirmations

Query GET /transfer with status=success and compare against your internal transfers table. Match on transfer_code. For each transfer, check recipient_integration.recipient_code and the actual amount. Store transfer.success webhook data in a transfers table, then run a daily reconciliation that calls GET /transfer/:id to confirm the final bank-side status. Flag any transfer with status=reversed for manual review.

In this article

  1. 1. Transfer Lifecycle: Dispatch vs Confirmation
  2. 2. Transfers Database Table
  3. 3. Handling Transfer Webhooks
  4. 4. Daily Reconciliation Against Paystack API
  5. 5. Learn More

Transfer Lifecycle: Dispatch vs Confirmation

There are two distinct events in a Paystack transfer:

  1. transfer.success webhook — Paystack processed the transfer and it left your balance.
  2. Bank credit — The receiving bank processes the deposit. This can take minutes to hours depending on the bank and transfer type.

Paystack sends one transfer.success when its processing is done. You do not receive a separate event when the recipient's bank credits the account. If the bank rejects it, Paystack fires transfer.reversed and refunds your balance.

Your reconciliation must account for both the initial success and any subsequent reversal.

Transfers Database Table

-- Store every transfer event in this table
CREATE TABLE transfers (
  id SERIAL PRIMARY KEY,
  transfer_code TEXT UNIQUE NOT NULL,
  reference TEXT,
  recipient_code TEXT NOT NULL,
  recipient_name TEXT,
  bank_name TEXT,
  account_number TEXT,
  amount NUMERIC(12, 2) NOT NULL,
  currency TEXT NOT NULL DEFAULT 'NGN',
  status TEXT NOT NULL DEFAULT 'pending',
  -- 'pending' | 'success' | 'reversed' | 'failed'
  initiated_at TIMESTAMPTZ,
  completed_at TIMESTAMPTZ,
  reversed_at TIMESTAMPTZ,
  paystack_created_at TIMESTAMPTZ,
  notes TEXT,
  raw_webhook JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Handling Transfer Webhooks

// Webhook handler for transfer events
if (event.event === 'transfer.success') {
  var t = event.data;
  await db.transfers.upsert({
    transfer_code: t.transfer_code,
    reference: t.reference,
    recipient_code: t.recipient.recipient_code,
    recipient_name: t.recipient.details?.account_name,
    bank_name: t.recipient.details?.bank_name,
    account_number: t.recipient.details?.account_number,
    amount: t.amount / 100,
    currency: t.currency,
    status: 'success',
    completed_at: t.updated_at,
    raw_webhook: t,
  }, { conflictField: 'transfer_code' });
}

if (event.event === 'transfer.reversed') {
  await db.transfers.update(
    { status: 'reversed', reversed_at: event.data.updated_at },
    { where: { transfer_code: event.data.transfer_code } }
  );
  // Alert: money returned to balance — needs manual review or re-payout
  console.error('Transfer reversed:', event.data.transfer_code, event.data.failure_reason);
}

if (event.event === 'transfer.failed') {
  await db.transfers.update(
    { status: 'failed' },
    { where: { transfer_code: event.data.transfer_code } }
  );
}

Daily Reconciliation Against Paystack API

// reconcile-transfers.js — run daily
async function reconcileTransfers() {
  // 1. Fetch all transfers from Paystack for the last 7 days
  var response = await fetch(
    'https://api.paystack.co/transfer?perPage=100&page=1',
    { headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY } }
  );
  var { data } = await response.json();

  var mismatches = [];

  for (var pt of data) {
    var localTransfer = await db.transfers.findOne({
      where: { transfer_code: pt.transfer_code }
    });

    if (!localTransfer) {
      mismatches.push({ type: 'missing_local', transfer_code: pt.transfer_code });
      continue;
    }

    // Status mismatch
    if (localTransfer.status !== pt.status) {
      mismatches.push({
        type: 'status_mismatch',
        transfer_code: pt.transfer_code,
        local_status: localTransfer.status,
        paystack_status: pt.status
      });
      // Update local to match Paystack
      await db.transfers.update(
        { status: pt.status },
        { where: { transfer_code: pt.transfer_code } }
      );
    }

    // Amount mismatch
    var paystackAmount = pt.amount / 100;
    if (Math.abs(localTransfer.amount - paystackAmount) > 0.01) {
      mismatches.push({
        type: 'amount_mismatch',
        transfer_code: pt.transfer_code,
        local: localTransfer.amount,
        paystack: paystackAmount
      });
    }
  }

  if (mismatches.length > 0) {
    console.error('Transfer reconciliation mismatches:', mismatches);
    // Send alert to Slack/email
  }

  return { total: data.length, mismatches: mismatches.length };
}

Learn More

This guide is part of the Paystack transfers and payouts series.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓transfer.success means Paystack dispatched the transfer — not that the recipient bank credited the account.
  • ✓The final confirmed status comes via the transfer.success webhook once bank processing completes (usually minutes to hours).
  • ✓Reversals (transfer.reversed) happen when the bank rejects the transfer — usually wrong account number or account closed.
  • ✓Build a transfers table: store transfer_code, recipient_code, amount, status, and timestamps from every transfer webhook.
  • ✓Run a daily reconciliation: call GET /transfer to list all transfers, compare amounts and statuses against your transfers table.
  • ✓Flag mismatches — a transfer in your DB as success but reversed in Paystack means money came back to your balance silently.

Frequently Asked Questions

Does Paystack notify me when the recipient actually receives the money?
No. Paystack fires transfer.success when the transfer is processed on its end. There is no separate event for bank-side crediting. If the bank rejects the transfer, Paystack fires transfer.reversed and refunds your balance.
How long does it take for a Paystack transfer to reach the recipient?
NGN bank transfers typically settle within minutes to a few hours for same-bank, and up to next business day for other banks. GHs mobile money and bank transfers are usually faster. Always communicate expectations to recipients rather than exact times.
What should I do when a transfer is reversed?
Mark the transfer as reversed in your database, refund the recipient in your system (their vendor balance should go back up), and optionally trigger a re-payout attempt with a corrected account number. Log the failure_reason from the webhook — it usually says "Account Not Found" or "Account Closed".
Can I query Paystack for all transfers in a date range?
Yes. Call GET /transfer?from=2026-07-01&to=2026-07-22&perPage=100. Paginate through all pages using the page parameter. Use this for your daily reconciliation script to compare Paystack's records against your database.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Paystack Transfers and Payouts: Complete Guide

Everything you need to send money out of your Paystack account: creating transfer recipients, initiating single and bulk transfers, handling OTP requirements, managing failures, and building automated payout systems for bank accounts, M-Pesa wallets, and mobile money across Africa.

Read more

Transfer OTP: Enabling, Disabling and Automating

Paystack requires OTP confirmation for transfers by default. This guide explains the OTP flow, how to disable it for automated payout systems, the security trade-offs of disabling OTP, and how to build your own application-level approval controls to replace OTP with something better suited to production automation.

Read more

Funding Your Paystack Balance Programmatically

Your Paystack balance needs funds before you can send transfers. This guide covers the different ways to fund your balance, setting up dedicated virtual accounts, monitoring balance levels, building automated funding alerts, and integrating balance management into your payout pipeline so you never run out mid-payout.

Read more

Building a Vendor Payout Portal on Paystack

Your vendors need visibility into their earnings and payouts. This guide covers building a vendor-facing payout portal: an earnings dashboard showing pending and paid amounts, payout history with status tracking, self-service bank detail management, on-demand withdrawal requests, and the backend architecture to support it all.

Read more

Auditing a Paystack Integration You Inherited

You just inherited a Paystack integration from a previous developer. Does it verify transactions server-side? Does it have the double-grant bug? Are the API keys secure? This guide is a systematic checklist for auditing an existing Paystack integration, finding the bugs that lose money, and fixing them.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.