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. Build a Ticketed Webinar Platform
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Build a Ticketed Webinar Platform

Build a ticketed webinar platform by creating events with capacity limits and pricing tiers, generating a unique access token for each attendee in the charge.success webhook, and using that token to authenticate entry to the live session and replay. Block access for attendees without a valid, paid token.

In this article

  1. 1. Event Setup and Ticket Purchase
  2. 2. Live Access Control and Replay Management
  3. 3. Learn More

Event Setup and Ticket Purchase

Tables: events (title, description, host_id, scheduled_at, timezone, price, early_bird_price, early_bird_deadline, capacity, seats_sold, status, video_platform, video_room_url), tickets (event_id, attendee_email, attendee_name, access_token, price_paid, paystack_ref, issued_at, used_for_live, used_for_replay).

var crypto = require('crypto');

function generateAccessToken() {
  return crypto.randomBytes(24).toString('hex'); // 48-char unique token
}

async function getCurrentPrice(event) {
  if (event.early_bird_deadline && new Date() < new Date(event.early_bird_deadline)) {
    return event.early_bird_price;
  }
  return event.price;
}

async function purchaseTicket(attendeeEmail, attendeeName, eventId) {
  var event = await db.events.findById(eventId);
  if (event.status !== 'on_sale') throw new Error('Tickets not available');
  if (event.seats_sold >= event.capacity) throw new Error('Event is sold out');

  var price = await getCurrentPrice(event);
  var reference = 'WEB-' + eventId + '-' + Date.now();

  var res = 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: attendeeEmail,
      amount: price,
      currency: 'NGN',
      reference,
      metadata: { event_id: eventId, attendee_name: attendeeName, attendee_email: attendeeEmail },
    }),
  });
  return (await res.json()).data.authorization_url;
}

// Webhook: issue ticket with access token
if (event.event === 'charge.success') {
  var meta = event.data.metadata;
  if (!meta.event_id) return res.sendStatus(200);

  var webinarEvent = await db.events.findById(meta.event_id);
  if (webinarEvent.seats_sold >= webinarEvent.capacity) {
    // Race condition: sold out — refund
    await fetch('https://api.paystack.co/refund', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ transaction: event.data.reference }) });
    return res.sendStatus(200);
  }

  var accessToken = generateAccessToken();
  await db.tickets.create({
    event_id: meta.event_id,
    attendee_email: meta.attendee_email,
    attendee_name: meta.attendee_name,
    access_token: accessToken,
    price_paid: event.data.amount,
    paystack_ref: event.data.reference,
    issued_at: new Date(),
  });
  await db.events.increment(meta.event_id, 'seats_sold', 1);
  await sendTicketEmail(meta.attendee_email, meta.attendee_name, webinarEvent, accessToken);
}

Live Access Control and Replay Management

// Attendee joins via: /join?token=abc123
async function validateAccessToken(token, eventId, accessType) {
  var ticket = await db.tickets.findByToken(token);
  if (!ticket || ticket.event_id !== parseInt(eventId)) {
    throw { status: 403, message: 'Invalid or expired ticket.' };
  }

  var webinarEvent = await db.events.findById(eventId);

  if (accessType === 'live') {
    var now = new Date();
    var startTime = new Date(webinarEvent.scheduled_at);
    var endTime = new Date(startTime.getTime() + 180 * 60000); // 3-hour window
    if (now < new Date(startTime.getTime() - 15 * 60000)) throw { status: 403, message: 'Event has not started yet. Join 15 minutes before.' };
    if (now > endTime) throw { status: 403, message: 'Live session has ended. Access the replay instead.' };
    await db.tickets.update(ticket.id, { used_for_live: true, first_joined_at: new Date() });
  }

  if (accessType === 'replay') {
    if (webinarEvent.status !== 'replay_available') throw { status: 403, message: 'Replay not yet available.' };
    await db.tickets.update(ticket.id, { used_for_replay: true });
  }

  return { valid: true, room_url: webinarEvent.video_room_url };
}

// Refund before event (admin or automated)
async function refundTicket(ticketId) {
  var ticket = await db.tickets.findById(ticketId);
  var event = await db.events.findById(ticket.event_id);
  var hoursUntilEvent = (new Date(event.scheduled_at) - new Date()) / (1000 * 60 * 60);
  if (hoursUntilEvent < 24) throw new Error('Refund window has passed — less than 24 hours to event.');

  await fetch('https://api.paystack.co/refund', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ transaction: ticket.paystack_ref }),
  });
  await db.tickets.update(ticketId, { status: 'refunded' });
  await db.events.decrement(ticket.event_id, 'seats_sold', 1);
}

Get weekly developer tips

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

No spam. Unsubscribe anytime.

Learn More

See build a cinema ticketing system for the seat reservation and QR code ticketing pattern applied to in-person events.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Generate a unique, cryptographically random access token per ticket in the charge.success webhook.
  • ✓Enforce capacity: decrement available_seats on payment confirmation; show "sold out" when zero.
  • ✓Support early-bird pricing: track early_bird_deadline and switch to full price automatically.
  • ✓Grant replay access to paid attendees using the same token — no separate replay purchase.
  • ✓Issue refunds up to 24 hours before the event; no refunds after the event starts.

Frequently Asked Questions

What video platform should I use for the live webinar?
Zoom Webinars (large audiences, registration-based), StreamYard (browser-based streaming to YouTube/Facebook), or Daily.co (embeddable, REST API) all work well. For simpler setups, create a private YouTube Live stream and share the URL only with valid ticket holders. The access token validates ticket ownership; the video platform handles the actual stream.
How do I handle free webinars vs paid webinars on the same platform?
Set price: 0 for free events. Skip the Paystack payment flow for free events — instead, collect just the email, generate an access token, and send the join link directly. Keep the same token-based access control for both free and paid events. This lets you track attendance for both types.
Can I run multiple sessions of the same webinar (cohorts)?
Yes. Create multiple event records with different scheduled_at times, each with their own capacity and ticket sales. Attendees buy a ticket for a specific session. If you want one payment to cover all sessions (a cohort), generate multiple access tokens (one per session date) in the webhook and include all of them in the welcome email.

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

Things You Can Build with Paystack: Project Guides

A complete map of real-world projects you can build using the Paystack API. From online store checkouts and subscription platforms to savings apps and solar pay-as-you-go systems, each category links to step-by-step build guides with working code.

Read more

Build a Parking Payment App

A build guide for a parking payment app where drivers scan a QR code on entry, pay for their session via Paystack (card or M-Pesa), and the system validates payment before the barrier lifts for exit. Covers time-based pricing, session management, and overstay handling.

Read more

Build a Referral Payout System

A build guide for a referral payout system where users share referral links, earn commissions when referrals convert, and withdraw their balance via Paystack Transfers to their bank account or M-Pesa wallet.

Read more

Build a Veterinary Clinic Booking and Payment System

A build guide for a veterinary clinic booking and payment system where pet owners book appointments online, pay a deposit via Paystack to confirm the slot, and pay the balance at the clinic after the consultation. Includes pet health records, vaccination schedules, and automated reminders.

Read more

Accepting M-Pesa Payments with the Paystack Charge API

Using the Paystack Charge API to accept M-Pesa payments without a redirect or popup. Covers the full charge flow from initiation to completion, handling the pending status, building a custom waiting UI, code examples in Node.js, differences from the standard Initialize flow, limitations, and when this approach makes sense.

Read more

Migrating from Manual Payouts to Paystack Splits

Many marketplaces start with manual bank transfers for vendor payouts — logging into internet banking every Friday to send money. Paystack splits automate this. This guide covers the migration path: creating subaccounts for existing vendors, running in parallel to validate, and cutting over to automated splits.

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.