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

Handle Paystack Webhooks in React Native

React Native apps cannot receive Paystack webhooks directly. Build a webhook endpoint on your backend (Express, Django, Laravel, etc.), verify the x-paystack-signature, and update your database. Notify the React Native app via Firebase Cloud Messaging push notifications, a polling endpoint, or deep links from the callback URL.

In this article

  1. 1. Why React Native Cannot Receive Webhooks
  2. 2. Backend Webhook Handler
  3. 3. React Native: Expo Push Notifications
  4. 4. Simpler Alternative: Polling
  5. 5. Learn More

Why React Native Cannot Receive Webhooks

Paystack webhooks are HTTP POST requests to an HTTPS URL. React Native apps run on mobile devices with no public IP address or domain. Paystack cannot reach your app directly.

The correct architecture:

Paystack → Your Backend (webhook received)
                 ↓
           Update Database
                 ↓
    Push Notification to React Native App
                 ↓
           App Updates UI

Backend Webhook Handler

// Node.js/Express
var crypto = require('crypto');

app.post('/api/webhooks/paystack',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    var sig = req.headers['x-paystack-signature'];
    var hash = crypto
      .createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
      .update(req.body)
      .digest('hex');

    if (hash !== sig) return res.status(400).send('Invalid signature');

    res.sendStatus(200); // Respond immediately

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

    if (event.event === 'charge.success') {
      var reference = event.data.reference;
      var email = event.data.customer.email;
      var amount = event.data.amount / 100;

      // Update database
      await db.orders.update({ reference }, { status: 'paid', paidAt: new Date() });

      // Send push notification to user
      var user = await db.users.findByEmail(email);
      if (user?.fcmToken) {
        await sendPushNotification(user.fcmToken, {
          title: 'Payment Confirmed',
          body: 'Your payment of NGN ' + amount + ' was received',
          data: { event: 'charge.success', reference },
        });
      }
    }
  }
);

Get weekly developer tips

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

No spam. Unsubscribe anytime.

React Native: Expo Push Notifications

// hooks/usePushNotifications.ts
import { useEffect } from 'react';
import * as Notifications from 'expo-notifications';
import { useNavigation } from '@react-navigation/native';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

export function usePushNotifications() {
  var navigation = useNavigation();

  useEffect(() => {
    // Register for push notifications
    registerForPushNotifications();

    // Handle notification received while app is open
    var subscription = Notifications.addNotificationReceivedListener(notification => {
      var data = notification.request.content.data;
      if (data.event === 'charge.success') {
        // Update payment status in local state
        console.log('Payment confirmed:', data.reference);
      }
    });

    // Handle notification tapped
    var responseSubscription = Notifications.addNotificationResponseReceivedListener(response => {
      var data = response.notification.request.content.data;
      if (data.event === 'charge.success') {
        navigation.navigate('OrderDetails', { reference: data.reference });
      }
    });

    return () => {
      subscription.remove();
      responseSubscription.remove();
    };
  }, []);
}

async function registerForPushNotifications() {
  var { status } = await Notifications.requestPermissionsAsync();
  if (status !== 'granted') return;

  var token = await Notifications.getExpoPushTokenAsync();
  // Send token to your backend
  await fetch('https://your-backend.com/api/users/push-token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token: token.data }),
  });
}

Simpler Alternative: Polling

// hooks/usePaymentStatus.ts
import { useState, useEffect, useRef } from 'react';

export function usePaymentStatus(reference: string | null) {
  var [status, setStatus] = useState<'pending' | 'success' | 'failed'>('pending');
  var timerRef = useRef<NodeJS.Timeout | null>(null);

  useEffect(() => {
    if (!reference) return;

    var attempts = 0;
    var maxAttempts = 10; // Poll for 30 seconds (every 3s)

    timerRef.current = setInterval(async () => {
      attempts++;

      var response = await fetch(
        'https://your-backend.com/api/pay/status?reference=' + reference
      );
      var data = await response.json();

      if (data.status === 'success') {
        setStatus('success');
        clearInterval(timerRef.current!);
      } else if (data.status === 'failed' || attempts >= maxAttempts) {
        setStatus(data.status === 'failed' ? 'failed' : 'pending');
        clearInterval(timerRef.current!);
      }
    }, 3000);

    return () => {
      if (timerRef.current) clearInterval(timerRef.current);
    };
  }, [reference]);

  return status;
}

Learn More

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

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Paystack webhooks hit your backend server, not your React Native app.
  • ✓Build the webhook handler on Node.js, Django, Laravel, or any backend framework.
  • ✓Use Firebase Cloud Messaging (FCM) or Expo Push Notifications to push payment events to React Native.
  • ✓For simpler setups, poll a backend status endpoint every few seconds after checkout.
  • ✓Deep links from the Paystack callback URL work for detecting redirect checkout completion.
  • ✓Always verify the webhook signature on your backend before taking any action.

Frequently Asked Questions

Can React Native apps receive Paystack webhooks?
No. Webhooks are sent to server-side HTTPS endpoints. React Native apps run on mobile devices with no public domain. Build a backend webhook handler and use push notifications or polling to inform the app.
Should I use Expo push notifications or Firebase FCM for Paystack events?
Both work. Expo Push Notifications are simpler for Expo projects. Firebase FCM gives more control and works for bare React Native. For a new project, Expo push notifications are faster to set up.
What if the user has notifications disabled?
Fall back to polling. After the checkout WebView closes, start polling your backend status endpoint every 3-5 seconds until you get a confirmed status. This handles both notification-disabled and foreground app scenarios.
How do I test webhooks during React Native development?
Run your backend locally and expose it with ngrok. Set the ngrok URL as your Paystack webhook URL. Your local backend receives the webhook and can push to the running React Native app via Metro/local network. This works well with Expo Go.

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 React Native

Accept Paystack payments in a React Native app using a WebView-based checkout. This guide covers using the react-native-paystack-webview library, building a custom WebView approach, and wiring up backend initialization and verification.

Read more

Verify Paystack Payments in React Native

After Paystack checkout in React Native, send the transaction reference to your backend to verify. This guide covers the full verification flow from the WebView callback to updating your UI.

Read more

Build Paystack Subscriptions in React Native

Add Paystack subscription billing to a React Native app. This guide covers initializing a subscription transaction from your backend, the checkout WebView flow, checking subscription status, and building subscription management UI.

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.