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. Paystack Bulk Charge: Charging Many Customers at Once
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Paystack Bulk Charge: Charging Many Customers at Once

The Paystack Bulk Charge API lets you submit an array of charges (each with an authorization_code, amount, and reference) in a single POST to /bulkcharge. Paystack processes them asynchronously and you poll the batch status or listen for webhooks to track individual charge results. Use it for subscription renewals, loan collections, or any scenario where you charge many saved cards on a schedule.

In this article

  1. 1. When to Use Bulk Charge
  2. 2. Submitting a Batch
  3. 3. Tracking Batch Status
  4. 4. Handling Mixed Results
  5. 5. Batch Size and Rate Limits
  6. 6. Designing References for Bulk Charges
  7. 7. Production Checklist
  8. 8. Stay Up to Date

When to Use Bulk Charge

Bulk charge is designed for scenarios where you need to charge many customers at the same time. Calling /transaction/charge_authorization in a loop for 500 customers works but is slow, hits rate limits, and requires you to manage concurrency yourself. Bulk charge handles this for you.

Common use cases:

  • Subscription renewals: On the 1st of the month, charge all active subscribers at once.
  • Loan repayments: Collect scheduled payments from all borrowers on the due date.
  • Savings deductions: Automated savings platforms that debit a fixed amount from each customer weekly or monthly.
  • Membership fees: Annual or monthly membership renewals for clubs, gyms, or associations.
  • Payroll deductions: Employer-deducted payments for insurance, savings schemes, or loan repayments.

If you are charging fewer than 10 customers, individual charge_authorization calls are simpler. Bulk charge adds value when the batch size makes individual calls impractical.

Submitting a Batch

Send a POST request to /bulkcharge with an array of charge objects:

async function submitBulkCharge(charges) {
  // charges is an array like:
  // [
  //   { authorization: 'AUTH_xxx', amount: 500000, reference: 'sub_001_jul2026' },
  //   { authorization: 'AUTH_yyy', amount: 500000, reference: 'sub_002_jul2026' },
  //   ...
  // ]

  var response = await fetch('https://api.paystack.co/bulkcharge', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(charges),
  });

  var data = await response.json();

  if (data.status) {
    // data.data.batch_code identifies this batch
    return {
      batchCode: data.data.batch_code,
      status: data.data.status,
    };
  }

  throw new Error('Bulk charge submission failed: ' + data.message);
}

// Build the charges array from your database
async function buildChargesForRenewal() {
  var subscribers = await db.subscriptions.findAll({
    where: { status: 'active', renewal_date: today() },
  });

  return subscribers.map(function(sub) {
    return {
      authorization: sub.authorization_code,
      amount: sub.plan_amount,
      reference: 'renewal_' + sub.id + '_' + currentMonth(),
    };
  });
}

// Submit
var charges = await buildChargesForRenewal();
var batch = await submitBulkCharge(charges);
console.log('Batch submitted: ' + batch.batchCode);

The API returns immediately. Paystack processes the charges in the background. The batch_code is your handle for tracking progress.

Get weekly developer tips

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

No spam. Unsubscribe anytime.

Tracking Batch Status

After submitting, you can poll the batch status and query individual charge results:

// Check overall batch status
async function checkBatchStatus(batchCode) {
  var response = await fetch(
    'https://api.paystack.co/bulkcharge/' + batchCode,
    {
      headers: {
        Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      },
    }
  );

  var data = await response.json();
  // data.data.status: 'active', 'paused', 'complete', 'failed'
  return data.data;
}

// Get individual charge results within a batch
async function getBatchCharges(batchCode) {
  var response = await fetch(
    'https://api.paystack.co/bulkcharge/' + batchCode + '/charges',
    {
      headers: {
        Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      },
    }
  );

  var data = await response.json();
  // data.data is an array of individual charge results
  return data.data;
}

// Poll until complete
async function waitForBatchCompletion(batchCode) {
  var maxAttempts = 60;
  var attempt = 0;

  while (attempt < maxAttempts) {
    var batch = await checkBatchStatus(batchCode);

    if (batch.status === 'complete') {
      return await getBatchCharges(batchCode);
    }

    if (batch.status === 'failed') {
      throw new Error('Batch failed');
    }

    // Wait 30 seconds before checking again
    await new Promise(function(resolve) { setTimeout(resolve, 30000); });
    attempt++;
  }

  throw new Error('Batch processing timed out');
}

You can also rely on webhooks instead of polling. Paystack sends charge.success and charge.failed events for each individual charge in the batch.

Handling Mixed Results

In any batch, some charges will succeed and some will fail. Your code must handle each outcome independently:

async function processResults(batchCode) {
  var charges = await getBatchCharges(batchCode);
  var succeeded = [];
  var failed = [];

  for (var i = 0; i < charges.length; i++) {
    var charge = charges[i];

    if (charge.status === 'success') {
      succeeded.push(charge);
      // Activate/renew the subscription
      await db.subscriptions.renew(charge.reference);
    } else {
      failed.push(charge);
      // Mark subscription as payment failed
      await db.subscriptions.markPaymentFailed(charge.reference, charge.gateway_response);
    }
  }

  console.log('Succeeded: ' + succeeded.length);
  console.log('Failed: ' + failed.length);

  // Handle failures: retry, notify, or downgrade
  for (var j = 0; j < failed.length; j++) {
    var failedCharge = failed[j];
    await scheduleRetry(failedCharge.reference);
    await notifyCustomerPaymentFailed(failedCharge.reference);
  }

  return { succeeded: succeeded, failed: failed };
}

Common failure reasons in bulk charge:

  • Insufficient funds: The most common. The customer does not have enough money.
  • Invalid authorization: The auth code has expired or the card was replaced.
  • Bank decline: The customer's bank declined the charge for various reasons.
  • Duplicate reference: You submitted the same reference twice in the batch or a reference that was already used.

Batch Size and Rate Limits

Paystack imposes limits on batch size and processing rate. The exact limits can change, so check the current documentation. General guidelines:

  • There is a maximum number of charges per batch (typically in the hundreds to low thousands).
  • If you need to charge more than the limit allows, split your charges into multiple batches and submit them sequentially.
  • Processing time scales with batch size. A batch of 50 charges completes faster than a batch of 500.
// Split large lists into batches
function chunkArray(array, chunkSize) {
  var chunks = [];
  for (var i = 0; i < array.length; i += chunkSize) {
    chunks.push(array.slice(i, i + chunkSize));
  }
  return chunks;
}

async function submitLargeBulkCharge(allCharges) {
  var batchSize = 500; // Check Paystack docs for current limit
  var chunks = chunkArray(allCharges, batchSize);
  var batches = [];

  for (var i = 0; i < chunks.length; i++) {
    var batch = await submitBulkCharge(chunks[i]);
    batches.push(batch);
    console.log('Submitted batch ' + (i + 1) + ' of ' + chunks.length);
  }

  return batches;
}

Designing References for Bulk Charges

Each charge in a bulk batch needs a unique reference. Good references for bulk charges include enough information to identify the customer, the billing period, and the charge type.

// Pattern: type_customerId_period
var reference = 'renewal_' + subscription.id + '_2026-07';
// Example: renewal_sub_142_2026-07

// Pattern: type_customerId_timestamp
var reference = 'loan_' + loan.id + '_' + Date.now();
// Example: loan_ln_089_1721472000000

Including the billing period in the reference prevents duplicate charges. If you accidentally submit the same batch twice, all references will already exist and Paystack will reject the duplicates. This is your safety net against double-charging customers.

For more on reference design, see transaction reference design patterns.

Production Checklist

Before running bulk charges in production:

  1. Test with a small batch first: Submit 2-3 test charges in test mode to verify your submission, polling, and result handling code works.
  2. Validate references: Ensure all references are unique within the batch and across previous batches for the same billing period.
  3. Have a retry strategy: Failed charges should be retried (with a delay) a limited number of times before you escalate to customer notification.
  4. Log everything: Log the batch code, individual results, and any errors. You will need this for debugging and for responding to customer inquiries.
  5. Monitor webhook delivery: Bulk charge generates many webhooks in a short time. Make sure your webhook endpoint can handle the load.
  6. Run at off-peak hours: If you have thousands of charges, submit them during off-peak hours to reduce the impact of any processing delays.

For webhook handling at scale, see the complete webhook engineering guide.

Stay Up to Date

Paystack updates bulk charge limits and features periodically. We keep these guides current.

Sign up for the McTaba newsletter to stay informed about Paystack API changes.

Key Takeaways

  • ✓Bulk charge accepts an array of charge objects, each with an authorization_code, amount, and reference. You submit them all in one API call.
  • ✓Processing is asynchronous. The API returns immediately with a batch ID. You poll the batch status or listen for webhooks to track results.
  • ✓Each charge in the batch is processed independently. Some may succeed, some may fail. Your code must handle mixed results.
  • ✓Bulk charge only works with saved authorization codes. You cannot use it for first-time card payments.
  • ✓Include meaningful references in each charge so you can map results back to specific customers or orders.
  • ✓Rate limits and batch size limits apply. Check Paystack documentation for current limits and plan your batches accordingly.

Frequently Asked Questions

Can I pause or cancel a bulk charge batch?
Paystack provides endpoints to pause and resume a bulk charge batch. You can pause processing if you discover an issue and resume once resolved. Check the Paystack documentation for the exact endpoints.
Does bulk charge support partial debits?
Bulk charge processes each charge as a standard charge_authorization call. Support for the at_least parameter in bulk charge may be limited. For partial debit support in batch scenarios, you may need to submit individual charge_authorization calls.
How long does bulk charge processing take?
Processing time depends on the batch size and Paystack server load. Small batches (under 100 charges) may complete in minutes. Large batches (thousands) can take significantly longer. Plan for the possibility that processing takes hours for very large batches.
What happens if I submit the same reference twice in a batch?
Paystack rejects duplicate references. If the same reference appears twice in a batch, one of them will fail. This is a safety feature that prevents double-charging.
Can I include metadata in bulk charge items?
Bulk charge items support a limited set of fields: authorization code, amount, and reference. For metadata, you would need to use individual charge_authorization calls instead, or track the additional data in your own database mapped to the reference.

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

Accepting Payments with Paystack: Complete Guide

Everything you need to accept your first payment with Paystack: choosing between Initialize Transaction and the Charge API, handling amounts in kobo, attaching metadata, setting callback URLs, and designing transaction references that keep your system clean.

Read more

Paystack Metadata: Passing Custom Data Through a Transaction

Paystack metadata lets you attach arbitrary JSON data to a transaction that flows through the entire lifecycle: from initialization to verification to webhooks. This guide shows you what to put in metadata, how to structure it for different use cases, and the limits you need to stay within.

Read more

Paystack Customer Objects: Create, Fetch, Update, Whitelist

Paystack creates a customer object every time a new email hits your checkout. You can also create customers explicitly, update their details, fetch their transaction history, and whitelist or blacklist them for risk control. This guide covers every operation with working Node.js code.

Read more

Building a Wallet Top-Up Flow on Paystack

A step-by-step guide to building wallet top-up flows with Paystack. Initialize payment, handle webhooks, credit wallets safely, enforce minimum amounts, and prevent double-crediting in production.

Read more

Paystack Webhook IP Allowlisting: How and When

IP allowlisting adds a second layer of security to your Paystack webhook endpoint by rejecting requests from any IP address that is not on Paystack's known list. This guide covers how to configure allowlisting in Nginx, cloud security groups, and your application, plus when it is worth the operational cost.

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.