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. Usage-Based Billing with Paystack
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Usage-Based Billing with Paystack

Paystack does not support usage-based billing natively. Build it with charge_authorization: track usage in your database throughout the billing period, calculate the amount at the end of the period, and charge the customer using their stored authorization code. You manage the usage tracking, the billing calculation, the charge scheduling, and the failure handling. Paystack just processes each charge.

In this article

  1. 1. What Is Usage-Based Billing
  2. 2. The Architecture
  3. 3. Tracking Usage Events
  4. 4. Calculating the Charge Amount
  5. 5. Running the Monthly Billing Cycle
  6. 6. Generating Detailed Usage Invoices
  7. 7. Real-Time Usage Dashboard for Customers

What Is Usage-Based Billing

Usage-based billing charges customers proportionally to their consumption. Instead of "5,000 NGN per month regardless of usage," it is "100 NGN per 1,000 API calls" or "50 NGN per GB of storage used."

Examples of products that use this model:

  • API platforms (charge per API call)
  • Messaging services (charge per message sent)
  • Cloud storage (charge per GB stored)
  • Transaction processors (charge per transaction processed)
  • Communication platforms (charge per SMS, per minute of call time)

The Paystack Subscriptions API cannot handle this because it charges a fixed amount. You need charge_authorization, where you control the amount on each charge.

This article is part of the subscriptions and recurring billing guide.

The Architecture

Usage-based billing has four components:

  1. Usage tracking: Record every billable event in real time.
  2. Billing calculator: At the end of the period, aggregate usage and calculate the charge.
  3. Charge execution: Call charge_authorization with the calculated amount.
  4. Invoice generation: Create a detailed invoice showing what the customer used and what they owe.
-- Usage events table
CREATE TABLE usage_events (
  id BIGSERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id),
  event_type VARCHAR(50) NOT NULL,
  quantity NUMERIC NOT NULL DEFAULT 1,
  metadata JSONB,
  recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_usage_events_user_period ON usage_events(user_id, recorded_at);
CREATE INDEX idx_usage_events_type ON usage_events(event_type, recorded_at);

-- Pricing rules
CREATE TABLE usage_pricing (
  id SERIAL PRIMARY KEY,
  plan_id INTEGER REFERENCES billing_plans(id),
  event_type VARCHAR(50) NOT NULL,
  included_units NUMERIC DEFAULT 0,
  price_per_unit INTEGER NOT NULL,
  currency VARCHAR(3) DEFAULT 'NGN',
  tier_start NUMERIC,
  tier_end NUMERIC
);

Get weekly developer tips

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

No spam. Unsubscribe anytime.

Tracking Usage Events

// Call this every time a billable event happens
async function trackUsage(userId, eventType, quantity) {
  await db.query(
    'INSERT INTO usage_events (user_id, event_type, quantity, recorded_at) VALUES ($1, $2, $3, NOW())',
    [userId, eventType, quantity || 1]
  );
}

// Examples of where you call trackUsage:
// In your API middleware:
app.use('/api/v1/', function(req, res, next) {
  if (req.user) {
    trackUsage(req.user.id, 'api_call', 1);
  }
  next();
});

// In your messaging service:
async function sendMessage(userId, recipient, content) {
  await messageQueue.send({ recipient: recipient, content: content });
  await trackUsage(userId, 'message_sent', 1);
}

// For storage, track the delta:
async function uploadFile(userId, fileSizeBytes) {
  await saveFile(userId, fileSizeBytes);
  var sizeGB = fileSizeBytes / (1024 * 1024 * 1024);
  await trackUsage(userId, 'storage_gb', sizeGB);
}

For high-volume products (thousands of API calls per second), batch usage events. Instead of inserting one row per API call, aggregate in memory and flush to the database every few seconds or every 100 events. Use a Redis counter for real-time tracking and sync to the database periodically.

Calculating the Charge Amount

async function calculateUsageBill(userId, periodStart, periodEnd) {
  var plan = await getUserPlan(userId);
  var pricing = await db.query(
    'SELECT event_type, included_units, price_per_unit FROM usage_pricing WHERE plan_id = $1',
    [plan.id]
  );

  var lineItems = [];
  var total = 0;

  for (var i = 0; i < pricing.rows.length; i++) {
    var rule = pricing.rows[i];

    // Get usage for this event type in the billing period
    var usage = await db.query(
      'SELECT COALESCE(SUM(quantity), 0) as total FROM usage_events WHERE user_id = $1 AND event_type = $2 AND recorded_at >= $3 AND recorded_at < $4',
      [userId, rule.event_type, periodStart, periodEnd]
    );

    var usedUnits = parseFloat(usage.rows[0].total);
    var billableUnits = Math.max(0, usedUnits - rule.included_units);
    var lineTotal = Math.round(billableUnits * rule.price_per_unit);

    lineItems.push({
      eventType: rule.event_type,
      totalUsed: usedUnits,
      includedUnits: rule.included_units,
      billableUnits: billableUnits,
      pricePerUnit: rule.price_per_unit,
      lineTotal: lineTotal,
    });

    total = total + lineTotal;
  }

  return {
    userId: userId,
    periodStart: periodStart,
    periodEnd: periodEnd,
    lineItems: lineItems,
    totalAmount: total,
  };
}

Handle edge cases:

  • Zero usage: Total is 0. Do not charge. Create a zero-amount invoice for records.
  • Minimum charge: Some plans have a minimum monthly fee. If usage-based charges are below the minimum, charge the minimum instead.
  • Usage within included quota: If the plan includes 10,000 API calls free, subtract those before calculating the billable amount.

Running the Monthly Billing Cycle

async function runUsageBilling() {
  var now = new Date();
  var periodEnd = new Date(now.getFullYear(), now.getMonth(), 1); // First of current month
  var periodStart = new Date(now.getFullYear(), now.getMonth() - 1, 1); // First of last month

  var usageAccounts = await db.query(
    'SELECT ba.user_id, ba.payment_method_id, u.email FROM billing_accounts ba JOIN users u ON ba.user_id = u.id WHERE ba.status = $1 AND ba.billing_type = $2',
    ['active', 'usage']
  );

  for (var i = 0; i < usageAccounts.rows.length; i++) {
    var account = usageAccounts.rows[i];

    try {
      var bill = await calculateUsageBill(account.user_id, periodStart, periodEnd);

      // Create invoice
      var invoice = await createUsageInvoice(account.user_id, bill);

      if (bill.totalAmount === 0) {
        console.log('Zero usage for user ' + account.user_id + '. No charge.');
        await db.query('UPDATE invoices SET status = $1, paid_at = NOW() WHERE id = $2', ['paid', invoice.id]);
        continue;
      }

      // Charge
      var authCode = await getAuthorizationCode(account.payment_method_id);
      var reference = 'USAGE_' + account.user_id + '_' + periodStart.toISOString().slice(0, 7);

      var result = await chargeAuthorization(account.email, bill.totalAmount, authCode, reference);

      if (result.status === 'success') {
        await db.query('UPDATE invoices SET status = $1, paid_at = NOW(), paystack_reference = $2 WHERE id = $3',
          ['paid', reference, invoice.id]);
      } else {
        await db.query('UPDATE invoices SET status = $1 WHERE id = $2', ['failed', invoice.id]);
        await startDunning(account.user_id, reference, result.gateway_response);
      }
    } catch (error) {
      console.log('Error billing user ' + account.user_id + ': ' + error.message);
    }
  }
}

Schedule this to run on the 1st of each month. Process in the early morning when API call volumes are lower. For larger customer bases, process in batches to avoid overwhelming Paystack with simultaneous charge requests.

Generating Detailed Usage Invoices

Usage invoices should itemize what the customer consumed. This transparency reduces billing disputes and builds trust.

async function createUsageInvoice(userId, bill) {
  var invoiceNumber = 'INV-' + userId + '-' + bill.periodStart.toISOString().slice(0, 7);

  var invoiceId = await db.query(
    'INSERT INTO invoices (billing_account_id, invoice_number, amount, currency, status, period_start, period_end, created_at) VALUES ((SELECT id FROM billing_accounts WHERE user_id = $1), $2, $3, $4, $5, $6, $7, NOW()) RETURNING id',
    [userId, invoiceNumber, bill.totalAmount, 'NGN', 'pending', bill.periodStart, bill.periodEnd]
  );

  var id = invoiceId.rows[0].id;

  for (var i = 0; i < bill.lineItems.length; i++) {
    var item = bill.lineItems[i];
    var description = item.eventType + ': ' + item.totalUsed + ' used';
    if (item.includedUnits > 0) {
      description = description + ' (' + item.includedUnits + ' included, ' + item.billableUnits + ' billed)';
    }

    await db.query(
      'INSERT INTO invoice_line_items (invoice_id, description, quantity, unit_amount, total_amount) VALUES ($1, $2, $3, $4, $5)',
      [id, description, item.billableUnits, item.pricePerUnit, item.lineTotal]
    );
  }

  return { id: id, number: invoiceNumber };
}

An example invoice might read:

  • API Calls: 45,230 used (10,000 included, 35,230 billed) at 10 kobo each: 3,523 NGN
  • Messages Sent: 1,200 used (500 included, 700 billed) at 500 kobo each: 3,500 NGN
  • Storage: 15.2 GB (5 GB included, 10.2 GB billed) at 10,000 kobo per GB: 10,200 NGN
  • Total: 17,223 NGN

Real-Time Usage Dashboard for Customers

Customers need to see their current usage and estimated bill before the billing date. Nobody wants a surprise charge.

// Backend API: GET /api/billing/usage
router.get('/api/billing/usage', async function(req, res) {
  var userId = req.user.id;
  var billing = await getCurrentBilling(userId);

  var periodStart = billing.current_period_start || new Date(new Date().getFullYear(), new Date().getMonth(), 1);
  var periodEnd = new Date(periodStart);
  periodEnd.setMonth(periodEnd.getMonth() + 1);

  var currentBill = await calculateUsageBill(userId, periodStart, new Date());

  var daysInPeriod = Math.ceil((periodEnd - periodStart) / (24 * 60 * 60 * 1000));
  var daysElapsed = Math.ceil((new Date() - periodStart) / (24 * 60 * 60 * 1000));
  var projectedMultiplier = daysInPeriod / Math.max(daysElapsed, 1);

  res.json({
    currentUsage: currentBill.lineItems,
    currentTotal: currentBill.totalAmount,
    projectedTotal: Math.round(currentBill.totalAmount * projectedMultiplier),
    periodStart: periodStart,
    periodEnd: periodEnd,
    daysRemaining: daysInPeriod - daysElapsed,
  });
});

Show the projected total prominently. If a customer is on pace to spend significantly more than last month, that is useful information they need before the bill arrives.

For metered billing with a base fee plus overages, see metered billing and overages.

Key Takeaways

  • ✓Paystack has no native usage-based billing. Build it on charge_authorization with your own usage tracking and billing calculation.
  • ✓Track usage events in real time. Store each event (API call, message, storage change) with a timestamp and the associated user.
  • ✓Calculate the charge amount at the end of the billing period based on accumulated usage. Apply any included quotas, tiered pricing, or minimum charges.
  • ✓Use charge_authorization to bill the calculated amount. The customer does not re-enter card details.
  • ✓Handle zero-usage periods gracefully. If the customer used nothing, skip the charge but create an invoice record.
  • ✓Usage-based billing has higher engineering complexity than fixed subscriptions. Budget for usage tracking infrastructure, billing calculators, and detailed invoices.

Frequently Asked Questions

Can I use the Paystack Subscriptions API for usage-based billing?
No. The Subscriptions API charges a fixed amount defined by the plan. For usage-based billing where the amount varies each cycle, use charge_authorization directly. You calculate the amount based on usage and charge it yourself.
What if the customer has zero usage in a billing period?
Skip the charge but create a zero-amount invoice for record-keeping. Some products have a minimum monthly charge even with zero usage. Implement this in your billing calculator by applying a floor amount.
How do I handle usage tracking at scale?
For high-volume products, use in-memory counters (Redis) for real-time tracking and flush aggregates to the database periodically. This avoids a database write for every single API call or event. Aggregate usage by minute or by batch size (every 100 events).
Should I bill at the beginning or end of the usage period?
Bill at the end (post-paid). The customer uses the service during the month, and you charge them at the start of the next month for what they consumed. Pre-paid usage billing is possible but requires usage estimation and credit/refund logic.
How do I prevent bill shock for customers?
Show real-time usage and projected costs in the customer dashboard. Send alerts when usage exceeds certain thresholds (50%, 80%, 100% of typical usage). Let customers set usage caps or spending limits.

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 Subscriptions and Recurring Billing: Complete Guide

Everything you need to build recurring billing on Paystack. Covers the subscriptions API, plans, authorization codes for manual charges, dunning, lifecycle webhooks, cancellation, and real-world patterns for SaaS, memberships, school fees, and loan collection across Africa.

Read more

Building Your Own Billing Engine on Paystack Authorizations

When the Subscriptions API is too rigid for your billing model, you build your own engine on top of Paystack authorization codes. This guide walks through the database schema, charge scheduler, billing state machine, retry queue, invoice generation, and reconciliation process you need to run reliable recurring billing.

Read more

Metered Billing and Overages on Paystack

Metered billing combines a fixed base fee with variable overage charges. The base fee covers an included quota. Usage beyond the quota is charged separately. This guide shows how to implement this hybrid model on Paystack using the Subscriptions API for the base fee and charge_authorization for overages.

Read more

Charging a Saved Card with Paystack Authorization Codes

A complete walkthrough of the Paystack charge_authorization endpoint. Learn how to charge a customer's saved card without them re-entering card details, handle success and failure responses, build idempotent charges, and avoid the common mistakes that cause double charges or missed payments.

Read more

Handling Failed Recurring Charges and Dunning on Paystack

Recurring charges fail. Cards expire, accounts run dry, banks decline for opaque reasons. This guide covers every failure scenario in Paystack recurring billing, shows you how to read gateway responses, build a retry strategy, implement a dunning state machine, and handle the higher failure rates typical in African card markets.

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.