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. Handle Paystack Webhooks in Firebase Cloud Functions
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Handle Paystack Webhooks in Firebase Cloud Functions

Create a Firebase Cloud Function using onRequest (v2). Access req.rawBody for HMAC SHA512 verification with crypto.createHmac. Compare the hash against the x-paystack-signature header, send 200 immediately, then update Firestore using the Firebase Admin SDK.

In this article

  1. 1. Webhook Cloud Function
  2. 2. Setup and Deploy
  3. 3. Learn More

Webhook Cloud Function

// functions/src/index.ts
import { onRequest } from "firebase-functions/v2/https";
import { defineSecret } from "firebase-functions/params";
import { getFirestore } from "firebase-admin/firestore";
import { initializeApp } from "firebase-admin/app";
import * as crypto from "crypto";

initializeApp();

const paystackSecretKey = defineSecret("PAYSTACK_SECRET_KEY");

export const paystackWebhook = onRequest(
  { secrets: [paystackSecretKey] },
  async (req, res) => {
    if (req.method !== "POST") {
      res.status(405).send("Method not allowed");
      return;
    }

    const signature = req.headers["x-paystack-signature"] as string;
    const secretKey = paystackSecretKey.value();

    // req.rawBody is a Buffer available in Firebase Cloud Functions
    const hash = crypto
      .createHmac("sha512", secretKey)
      .update(req.rawBody)
      .digest("hex");

    if (hash !== signature) {
      res.status(400).send("Invalid signature");
      return;
    }

    // Return 200 before processing
    res.sendStatus(200);

    const event = req.body;
    const db = getFirestore();

    // Idempotency check
    const eventId = event.data?.id?.toString() ?? event.data?.reference;
    if (eventId) {
      const processedRef = db.collection("webhook_events").doc(eventId);
      const existing = await processedRef.get();
      if (existing.exists) return;
      await processedRef.set({
        event_type: event.event,
        processed_at: new Date().toISOString(),
      });
    }

    switch (event.event) {
      case "charge.success": {
        const reference = event.data.reference;
        const amount = event.data.amount / 100;
        await db.collection("orders").doc(reference).set({
          status: "paid",
          paid_at: new Date().toISOString(),
          amount_paid: amount,
          currency: event.data.currency,
          customer_email: event.data.customer.email,
        }, { merge: true });
        break;
      }

      case "transfer.success": {
        const transferCode = event.data.transfer_code;
        await db.collection("transfers")
          .where("transfer_code", "==", transferCode)
          .get()
          .then(snapshot => {
            snapshot.forEach(doc => doc.ref.update({ status: "success" }));
          });
        break;
      }

      case "invoice.payment_failed": {
        const email = event.data.customer.email;
        await db.collection("subscriptions")
          .where("email", "==", email)
          .get()
          .then(snapshot => {
            snapshot.forEach(doc => doc.ref.update({ status: "past_due" }));
          });
        break;
      }
    }
  }
);

Setup and Deploy

# Set Paystack secret key as a Firebase secret
firebase functions:secrets:set PAYSTACK_SECRET_KEY

# Deploy only the webhook function
firebase deploy --only functions:paystackWebhook

After deploying, add the function URL to your Paystack dashboard webhook settings:

https://<region>-<project-id>.cloudfunctions.net/paystackWebhook

Create the Firestore security rules for webhook_events (server-only access):

match /webhook_events/{eventId} {
  allow read, write: if false; // Only Admin SDK can access
}

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

This guide is part of the Paystack integration guides by language and framework series.

Next: Build Paystack Subscriptions in Firebase Cloud Functions

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Firebase Cloud Functions provide req.rawBody (Buffer) when content-type is application/json — use it for HMAC verification.
  • ✓Use crypto.createHmac("sha512", secretKey).update(req.rawBody).digest("hex") for the signature check.
  • ✓Send res.sendStatus(200) before processing the event — Paystack retries on slow or failed responses.
  • ✓Mark events as processed in Firestore (webhook_events collection) to prevent duplicate handling on retries.
  • ✓Handle charge.success, transfer.success, subscription.create, and invoice.payment_failed as a minimum.
  • ✓Use Firebase Cloud Messaging in the same function to push payment notifications to mobile apps.

Frequently Asked Questions

Is req.rawBody available in Firebase Cloud Functions v2?
Yes. Firebase Cloud Functions (both v1 and v2) provide req.rawBody as a Buffer when the incoming request has Content-Type: application/json. This is a Firebase-specific addition to the standard Express request object. Use it for Paystack HMAC signature verification instead of re-reading the body.
Why not use req.body (the parsed JSON) for signature verification?
Paystack computes the HMAC over the exact raw bytes it sent. If you serialize req.body back to a string (JSON.stringify), the byte order or whitespace might differ. Always use req.rawBody (the original Buffer) for accurate HMAC computation.
How do I test Paystack webhooks locally with Firebase Functions?
Run firebase emulators:start --only functions. Expose your local emulator with ngrok: ngrok http 5001. The webhook URL will be http://<ngrok-id>.ngrok.io/<project-id>/<region>/paystackWebhook. Use the Paystack webhook simulator or curl to send test events.
Can I send a Firebase push notification from the same webhook function?
Yes. After updating Firestore, call getMessaging().send() from firebase-admin/messaging. Pass the user's FCM token stored in Firestore. This lets you push payment confirmed notifications to Android or iOS apps in the same Cloud Function that handles the Paystack webhook.

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 Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Accept Payments with Paystack in Firebase Cloud Functions

Firebase Cloud Functions provide a Node.js backend for Paystack without a dedicated server. This guide covers setting up a Cloud Function to initialize Paystack transactions, storing secrets securely, configuring CORS, and calling from a frontend.

Read more

Verify Paystack Payments in Firebase Cloud Functions

Verify Paystack payments server-side using Firebase Cloud Functions. This guide covers reading the transaction reference, calling the Paystack verify endpoint, validating the amount against Firestore, and updating the order status.

Read more

Build Paystack Subscriptions in Firebase Cloud Functions

Add recurring billing to a Firebase project using Paystack subscriptions and Cloud Functions. This guide covers subscription checkout with a plan code, webhook handling for subscription.create and invoice.payment_failed, and checking subscription status from Firestore.

Read more

Idempotent Paystack Webhook Handlers: The Complete Pattern

A complete guide to building idempotent Paystack webhook handlers. Covers why Paystack sends duplicate events, using transaction references as idempotency keys, database-level deduplication with a processed_events table, and the full production-ready pattern in Node.js.

Read more

Why You Must Return 200 Immediately from a Paystack Webhook

The number one Paystack webhook reliability rule: return 200 before doing anything else. This guide explains why Paystack has a timeout, what happens when you break the rule, and how to implement the respond-first-then-queue pattern in Node.js.

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.