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 Crowdfunding Platform
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Build a Crowdfunding Platform

Build a crowdfunding platform by letting creators set campaigns with targets and deadlines, collecting pledges through Paystack transactions, and either releasing funds to the creator when the target is met (all-or-nothing) or settling continuously (keep-it-all). Use Paystack metadata to track which campaign each pledge belongs to, and the Transfers API to pay creators after successful campaigns.

In this article

  1. 1. Architecture Overview
  2. 2. Data Model
  3. 3. Collecting Pledges
  4. 4. Webhook Handling
  5. 5. Campaign Deadline Processing
  6. 6. Reward Tier Fulfillment
  7. 7. Platform Fees and Financial Reporting
  8. 8. Deployment and Testing

Architecture Overview

A crowdfunding platform connects creators who need funding with backers who want to support them. The key architectural difference from a donation platform is the concept of a campaign target and a deadline. In all-or-nothing crowdfunding (the Kickstarter model), if the target is not reached by the deadline, every backer gets refunded. In keep-it-all crowdfunding, the creator receives whatever was raised.

The all-or-nothing model is harder to build because you need to hold funds until the deadline, then either pay the creator or refund everyone. The keep-it-all model is simpler because you can settle funds to the creator as they come in.

The system flow for all-or-nothing:

  1. Creator launches a campaign with a title, description, target, deadline, and reward tiers
  2. Backers select a reward tier and pay through Paystack
  3. Funds are collected and held in your Paystack balance
  4. When the deadline arrives, your system checks if the target was met
  5. If yes: transfer funds to the creator minus your platform fee
  6. If no: refund all backers

The tech stack is Node.js with Express, PostgreSQL, and a cron job (or scheduled task) that runs at campaign deadlines to trigger payouts or refunds.

Data Model

You need five tables: creators, campaigns, reward_tiers, pledges, and payouts.

The campaigns table holds:

  • id: UUID
  • creator_id: Who created the campaign
  • title, description, cover_image_url: Campaign presentation
  • target_amount: Funding goal in smallest currency unit
  • raised_amount: Total raised so far (updated by webhooks)
  • currency: KES, NGN, GHS, ZAR
  • funding_model: all_or_nothing or keep_it_all
  • deadline: Timestamp when the campaign closes
  • status: draft, active, funded, failed, paid_out
  • backer_count: Number of confirmed backers

The reward_tiers table stores campaign rewards:

  • campaign_id: Parent campaign
  • title: Reward name (e.g., "Early Bird Special")
  • description: What the backer gets
  • amount: Minimum pledge for this tier
  • max_backers: Limit on how many can claim this (nullable for unlimited)
  • claimed_count: How many have pledged at this tier

The pledges table records each backer contribution:

  • backer_email: Backer email
  • campaign_id: Which campaign
  • reward_tier_id: Which tier they selected
  • amount: Pledge amount (may exceed the tier minimum)
  • paystack_reference: Transaction reference
  • paystack_transaction_id: For refund processing
  • status: pending, completed, refunded

Get weekly developer tips

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

No spam. Unsubscribe anytime.

Collecting Pledges

When a backer selects a reward tier and confirms their pledge:

var axios = require("axios");

async function createPledge(backerEmail, campaignId, rewardTierId, amount) {
  var campaign = await db.query("SELECT * FROM campaigns WHERE id = $1 AND status = $2", [campaignId, "active"]);
  if (!campaign) throw new Error("Campaign not active");

  if (new Date(campaign.deadline) < new Date()) throw new Error("Campaign has ended");

  var tier = await db.query("SELECT * FROM reward_tiers WHERE id = $1", [rewardTierId]);
  if (amount < tier.amount) throw new Error("Pledge must be at least " + tier.amount);

  if (tier.max_backers && tier.claimed_count >= tier.max_backers) {
    throw new Error("This reward tier is sold out");
  }

  var reference = "PLG-" + campaignId.slice(0, 8) + "-" + Date.now();

  var response = await axios.post(
    "https://api.paystack.co/transaction/initialize",
    {
      email: backerEmail,
      amount: amount,
      currency: campaign.currency,
      reference: reference,
      callback_url: "https://yourplatform.com/campaigns/" + campaignId + "/thank-you",
      metadata: {
        campaign_id: campaignId,
        reward_tier_id: rewardTierId,
        backer_email: backerEmail,
        campaign_title: campaign.title
      }
    },
    {
      headers: {
        Authorization: "Bearer " + process.env.PAYSTACK_SECRET_KEY
      }
    }
  );

  await db.query(
    "INSERT INTO pledges (backer_email, campaign_id, reward_tier_id, amount, paystack_reference, status) VALUES ($1, $2, $3, $4, $5, $6)",
    [backerEmail, campaignId, rewardTierId, amount, reference, "pending"]
  );

  return response.data.data.authorization_url;
}

For all-or-nothing campaigns, the money is charged immediately (not just authorized). It sits in your Paystack balance until you decide what to do with it at deadline time. This is simpler than using preauthorization, which has a limited hold period that might expire before the campaign deadline.

Webhook Handling

var crypto = require("crypto");

router.post("/webhooks/paystack", express.raw({ type: "application/json" }), async function(req, res) {
  var hash = crypto
    .createHmac("sha512", process.env.PAYSTACK_SECRET_KEY)
    .update(req.body)
    .digest("hex");

  if (hash !== req.headers["x-paystack-signature"]) {
    return res.status(401).send("Invalid signature");
  }

  var event = JSON.parse(req.body);

  if (event.event === "charge.success") {
    var meta = event.data.metadata;
    var pledge = await db.query(
      "SELECT * FROM pledges WHERE paystack_reference = $1",
      [event.data.reference]
    );

    if (!pledge || pledge.status === "completed") {
      return res.status(200).send("OK");
    }

    await db.query(
      "UPDATE pledges SET status = $1, paystack_transaction_id = $2 WHERE id = $3",
      ["completed", event.data.id, pledge.id]
    );

    // Update campaign totals
    await db.query(
      "UPDATE campaigns SET raised_amount = raised_amount + $1, backer_count = backer_count + 1 WHERE id = $2",
      [pledge.amount, pledge.campaign_id]
    );

    // Update reward tier claimed count
    await db.query(
      "UPDATE reward_tiers SET claimed_count = claimed_count + 1 WHERE id = $1",
      [pledge.reward_tier_id]
    );

    await sendPledgeConfirmation(pledge.backer_email, meta.campaign_title, pledge.amount);
  }

  if (event.event === "refund.processed") {
    var reference = event.data.transaction.reference;
    await db.query(
      "UPDATE pledges SET status = $1 WHERE paystack_reference = $2",
      ["refunded", reference]
    );
  }

  res.status(200).send("OK");
});

Store the paystack_transaction_id (the numeric ID Paystack assigns to each transaction) in the pledge record. You need this for processing refunds later. The Refund API requires either the transaction ID or the reference.

Campaign Deadline Processing

At the campaign deadline, run a job that checks whether the target was met and either pays the creator or refunds backers:

async function processCampaignDeadline(campaignId) {
  var campaign = await db.query("SELECT * FROM campaigns WHERE id = $1", [campaignId]);

  if (campaign.status !== "active") return;

  if (campaign.funding_model === "all_or_nothing") {
    if (campaign.raised_amount >= campaign.target_amount) {
      // Campaign succeeded: pay the creator
      await db.query("UPDATE campaigns SET status = $1 WHERE id = $2", ["funded", campaignId]);
      await payCreator(campaign);
    } else {
      // Campaign failed: refund all backers
      await db.query("UPDATE campaigns SET status = $1 WHERE id = $2", ["failed", campaignId]);
      await refundAllBackers(campaignId);
    }
  } else {
    // keep_it_all: pay whatever was raised
    await db.query("UPDATE campaigns SET status = $1 WHERE id = $2", ["funded", campaignId]);
    if (campaign.raised_amount > 0) {
      await payCreator(campaign);
    }
  }
}

async function refundAllBackers(campaignId) {
  var pledges = await db.query(
    "SELECT * FROM pledges WHERE campaign_id = $1 AND status = $2",
    [campaignId, "completed"]
  );

  for (var i = 0; i < pledges.length; i++) {
    await axios.post(
      "https://api.paystack.co/refund",
      { transaction: pledges[i].paystack_transaction_id },
      {
        headers: {
          Authorization: "Bearer " + process.env.PAYSTACK_SECRET_KEY
        }
      }
    );
    // Small delay to avoid rate limiting
    await new Promise(function(resolve) { setTimeout(resolve, 500); });
  }
}

async function payCreator(campaign) {
  var creator = await db.query("SELECT * FROM creators WHERE id = $1", [campaign.creator_id]);
  var platformFee = Math.round(campaign.raised_amount * 0.05); // 5% platform fee
  var creatorPayout = campaign.raised_amount - platformFee;

  await axios.post(
    "https://api.paystack.co/transfer",
    {
      source: "balance",
      amount: creatorPayout,
      recipient: creator.paystack_recipient_code,
      reason: "Campaign payout: " + campaign.title,
      reference: "PAYOUT-" + campaign.id + "-" + Date.now()
    },
    {
      headers: {
        Authorization: "Bearer " + process.env.PAYSTACK_SECRET_KEY
      }
    }
  );

  await db.query("UPDATE campaigns SET status = $1 WHERE id = $2", ["paid_out", campaign.id]);
}

Run this function as a cron job that checks for campaigns past their deadline every hour. Alternatively, schedule a one-time job when each campaign is created, set to fire at the deadline timestamp.

Reward Tier Fulfillment

After a campaign is funded and the creator is paid, they need to fulfill reward promises. Build a fulfillment tracking system where the creator can mark each backer's reward as shipped, delivered, or fulfilled (for digital rewards).

Add a reward_fulfillments table with pledge_id, status (pending, shipped, delivered), tracking_number (for physical rewards), and timestamps. Give creators a dashboard where they can see all backers grouped by reward tier and update fulfillment status.

For digital rewards (early access to software, exclusive content), you can automate fulfillment. When the campaign is funded, generate access links or keys and send them to backers automatically.

Send backers an email when the campaign closes with their reward details: "The campaign reached its goal. Your [tier name] reward will be fulfilled by [estimated date]. Thank you for backing [campaign name]." Keep backers informed throughout the fulfillment process. The biggest complaint on crowdfunding platforms is silence after funding.

Platform Fees and Financial Reporting

Your platform revenue comes from the fee you deduct from successful campaign payouts. A standard model is 5% of the raised amount. Paystack also charges its own transaction fees on each pledge, which are separate from your platform fee.

Track three financial metrics per campaign: total pledged, total Paystack fees, and your platform fee. The creator payout is: total pledged minus Paystack fees (already deducted at transaction time) minus your platform fee.

Build an admin dashboard that shows all campaigns, their status, total platform revenue, and pending payouts. For accounting, export a monthly report with all completed campaigns, pledge amounts, refunds, platform fees earned, and creator payouts made.

For keep-it-all campaigns where you use subaccounts, the platform fee is handled automatically by the split. Your percentage_charge on the subaccount is set to (100 minus your fee percentage), and Paystack routes the money accordingly at settlement time.

Deployment and Testing

Test both funding models end to end in Paystack test mode:

  1. Create a campaign with a low target (e.g., 10,000 KES)
  2. Make enough test pledges to exceed the target
  3. Trigger the deadline processing and verify the creator payout
  4. Create another campaign and let it fail (do not reach the target)
  5. Trigger the deadline and verify all backers are refunded

Pay special attention to the refund flow for failed all-or-nothing campaigns. If you have 200 backers and need to refund them all, the process takes time and might encounter failures (expired cards, closed accounts). Build retry logic and flag any refunds that fail for manual review.

Deploy with a reliable cron scheduler. If the deadline processing job fails to run, campaigns will not close on time. Use a managed cron service or a process manager that restarts failed jobs. Log every deadline processing run with the campaign ID and outcome.

Key Takeaways

  • ✓Decide between all-or-nothing (refund everyone if the target is not met) and keep-it-all (creator gets whatever is raised). This decision shapes your entire payment architecture.
  • ✓For all-or-nothing campaigns, charge cards immediately but hold funds in your Paystack balance. Only transfer to the creator after the deadline if the target is met. Refund all backers if it is not.
  • ✓Use Paystack subaccounts for keep-it-all campaigns. Funds settle directly to the creator with your platform fee deducted automatically.
  • ✓Track pledges separately from completed payments. A pledge is an intent. A completed payment is confirmed money. Report campaign progress based on completed payments only.
  • ✓Reward tiers add complexity but increase backer engagement. A campaign with 3-5 reward levels (basic supporter, early bird, VIP) typically raises more than one with a single "donate any amount" option.
  • ✓Build a payout approval step. Before transferring funds to a creator, have an admin review the campaign to ensure it complies with your platform rules.

Frequently Asked Questions

Can I hold funds for all-or-nothing campaigns without a financial license?
This depends on your jurisdiction. In many African countries, holding customer funds for extended periods requires regulatory compliance. Consult a local fintech lawyer. One alternative is to use preauthorization (hold the charge on the card without capturing) but Paystack hold periods may be shorter than your campaign duration. Another option is to charge immediately and keep funds in your Paystack balance until the deadline.
How do I prevent fraudulent campaigns?
Implement a campaign review process. New campaigns go into "pending review" status. An admin reviews the campaign details, verifies the creator identity, and approves or rejects before it goes live. Also require identity verification for creators (KYC) before they can create their first campaign. This adds friction but protects your backers and your platform reputation.
Can backers increase their pledge after the initial payment?
Yes. Treat an increased pledge as a new transaction. The backer pays the difference through a new Paystack transaction. Create a new pledge record linked to the same campaign and reward tier. Their total contribution is the sum of all their pledges to that campaign.
What happens if a creator does not fulfill rewards?
This is the hardest problem in crowdfunding. Once funds are transferred to the creator, you have limited recourse. Mitigate this by holding a portion (say, 10%) of the payout until reward fulfillment is confirmed by backers, implementing a dispute resolution process, and banning creators who do not deliver. Be transparent with backers that pledge is not a purchase guarantee.

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 Laundry Pickup and Payment App

A build guide for a laundry pickup and delivery app that uses Paystack to collect payments. Covers order pricing by weight or item count, pickup scheduling, status tracking through the wash cycle, payment collection at order creation or delivery, and customer notifications.

Read more

Build an Affiliate Commission Engine

A build guide for an affiliate commission engine where affiliates share tracking links, earn commissions when their referrals convert (pay via Paystack), and withdraw their balance via bank transfer. Covers click tracking, last-click attribution, commission ledger, and Paystack Transfers payout.

Read more

Build a Farmers Market Ordering Platform

A build guide for a farmers market ordering platform where customers order fresh produce from multiple farmers in one cart, the platform collects the combined payment via Paystack, and each farmer receives their share via Paystack Split Payments or Transfers. Covers inventory management, order routing to individual farmers, and delivery coordination.

Read more

Migrating from Paystack to Direct Daraja

Sometimes the right move is going direct. This guide covers migrating from Paystack to Safaricom Daraja for M-Pesa payments. What you gain (cost, control, C2B validation), what you lose (cards, dashboard, simplicity), and a step-by-step migration plan with testing and rollback strategies.

Read more

Paystack Split Payments Explained

A clear explanation of how Paystack split payments work from the moment a customer pays to the moment each party receives their share. Covers the settlement flow, key API parameters, how Paystack calculates each party's portion, and the difference between single-split and multi-split transactions.

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.