Bonaventure OgetoBy Bonaventure Ogeto|

Handle Paystack Webhooks in React

React cannot handle Paystack webhooks. Webhooks are server-to-server HTTP POST requests that Paystack sends to a URL on your backend. A React app runs in the browser and has no server endpoint to receive these requests. You need a backend server (Node.js, Python, Go, or any server-side framework) to receive, verify, and process Paystack webhooks. Your React frontend then reads the results from your backend via API calls.

Why React Cannot Receive Webhooks

Webhooks are HTTP POST requests sent from Paystack's servers to a URL on your server. The keyword is "server." When Paystack sends a webhook for a successful charge, it makes a POST request to something like https://api.yourapp.com/webhooks/paystack. That URL must point to a running server process that can accept incoming HTTP connections.

React is a frontend library. It runs in the user's web browser. It does not have a server, a port, or a public URL. There is nowhere for Paystack to send the request. Even if you could somehow expose a user's browser to the internet, you would need every user's browser to be open and connected at the moment Paystack sends the webhook, which makes no sense.

This is not a React limitation specifically. The same is true for Vue, Angular, Svelte (without SvelteKit), and any other client-side framework. Webhooks are a backend concern.

The Correct Architecture

Here is how a properly architected Paystack integration works when your frontend is React:

  1. React (frontend): Initiates the payment using Paystack Popup or Paystack Inline. The customer enters their card details and completes the payment.
  2. Paystack: Processes the payment and sends a webhook POST request to your backend URL.
  3. Your backend: Receives the webhook, verifies the x-paystack-signature header using HMAC SHA512, processes the event (updates orders, credits accounts), and stores the result in your database.
  4. React (frontend): Polls your backend API or listens via WebSocket/Server-Sent Events to check the payment status and update the UI.

The frontend never touches the webhook. It does not know about it. The webhook is a private conversation between Paystack and your backend.

Customer Browser (React)
    |
    | 1. Initiates payment via Paystack Popup
    v
Paystack Servers
    |
    | 2. Sends webhook POST to your backend
    v
Your Backend Server (Express, Django, Rails, etc.)
    |
    | 3. Verifies signature, processes event, updates DB
    v
Database
    ^
    | 4. React app fetches updated state
    |
Customer Browser (React)

This separation is important for security too. Your Paystack secret key lives on the backend. It never goes to the frontend. If you put your secret key in React code, anyone can open browser DevTools and steal it.

What React Does After Payment

When a customer completes a payment through Paystack Popup, your React app receives a callback with a reference. This is useful for immediate UI feedback ("Payment successful! Redirecting..."), but you should never treat it as the final source of truth.

The callback happens in the browser. A user could manipulate it. They could close the browser before the callback fires. They could have network issues. The webhook, on the other hand, is server-to-server and reliable.

Here is a typical React flow after payment:

// React component using Paystack Popup
function PaymentButton({ amount, email }: { amount: number; email: string }) {
  const handleSuccess = (response: { reference: string }) => {
    // Show optimistic UI: "Payment received, verifying..."
    // Then poll your backend to confirm
    checkPaymentStatus(response.reference);
  };

  const checkPaymentStatus = async (reference: string) => {
    // Poll your backend, which was updated by the webhook
    const res = await fetch('/api/orders/status?ref=' + reference);
    const data = await res.json();

    if (data.status === 'paid') {
      // Payment confirmed by webhook on backend
      // Navigate to success page
    } else {
      // Still processing, poll again after a delay
      setTimeout(() => checkPaymentStatus(reference), 2000);
    }
  };

  // ... render payment button
}

The React app asks your backend "has this payment been confirmed?" Your backend knows because it received the webhook, verified the signature, and updated the order in the database.

Choose Your Backend Framework

You need a backend to handle Paystack webhooks. Here are guides for every major backend framework, each covering signature verification, raw body reading, and event routing:

Node.js / JavaScript / TypeScript

Python

PHP

Other languages

If your React app is a standalone SPA with no backend, you need to build one or use a serverless platform. The simplest option for React developers is usually Node.js with Express, or a Next.js API Route if you migrate to Next.js.

React with Next.js: The Full-Stack Option

If you want to keep everything in the React ecosystem, Next.js gives you both a React frontend and server-side API Routes (or Route Handlers in the App Router) in one project. You can handle Paystack webhooks in the same codebase as your React components.

The webhook handler lives in app/api/webhooks/paystack/route.ts (App Router) or pages/api/webhooks/paystack.ts (Pages Router). Your React components live in the app/ or pages/ directories as usual. They share the same deployment.

This is the most popular choice for React developers who want to handle Paystack webhooks without setting up a separate backend project. See the Next.js webhook guides linked above for full implementation details.

Other full-stack React options include Remix (which has server-side loaders and actions) and RedwoodJS (which includes a GraphQL API layer).

Common Mistakes to Avoid

Here are mistakes developers make when they do not understand the webhook architecture:

1. Trying to receive webhooks in the React app. Some developers try to set up a "webhook listener" in React. This is not possible. React runs in the browser. It cannot accept incoming HTTP connections from Paystack.

2. Trusting the Paystack Popup callback as the final confirmation. The onSuccess callback in Paystack Popup runs in the browser. A user could fake it. A network glitch could prevent it from firing. Always confirm payment status using the webhook on your backend.

3. Putting the Paystack secret key in React code. Your secret key must stay on the server. If you put it in environment variables that start with REACT_APP_ or NEXT_PUBLIC_, it gets bundled into the client-side JavaScript and is visible to anyone who opens DevTools.

4. Polling Paystack directly from the frontend. Some developers call the Paystack Verify Transaction API from the frontend to check payment status. This exposes your secret key. Always make this call from your backend.

5. Not handling the case where the webhook arrives before the redirect. Sometimes Paystack sends the webhook before the customer is redirected back to your site. Your backend should update the order status via webhook, and your React app should check for this status when the redirect page loads. For more on this, see race conditions between webhooks and redirect callbacks.

Real-Time Updates in React After Webhook Processing

After your backend processes a webhook, your React app needs to know about it. You have several options for keeping the frontend in sync:

Polling: The simplest approach. After the customer completes payment, your React app polls your backend every 2-3 seconds to check if the order status has been updated by the webhook.

WebSockets: Set up a WebSocket connection between your React app and backend. When the backend processes a webhook, it pushes a message to the connected client. Libraries like Socket.IO make this easy.

Server-Sent Events (SSE): Similar to WebSockets but simpler and one-directional. Your backend pushes updates to the React client when the webhook is processed.

Supabase Realtime: If you use Supabase, you can subscribe to database changes from your React app. When the webhook handler updates a row in the database, Supabase pushes the change to all subscribed clients in real time.

For most applications, polling is good enough. The customer sees "Verifying payment..." for 2-5 seconds, then the status updates. It is simple, reliable, and does not require any additional infrastructure.

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

Key Takeaways

  • React runs in the browser and cannot receive Paystack webhooks directly.
  • Webhooks are server-to-server POST requests. They need a backend endpoint with a public URL.
  • Your backend receives the webhook, verifies the signature, processes the event, and updates the database.
  • Your React frontend reads the updated state from your backend via API calls or real-time subscriptions.
  • Never verify payment status by trusting the frontend callback alone. Always use webhooks on the backend.
  • Pick a backend framework and follow the corresponding webhook guide for the actual implementation.

Frequently Asked Questions

Can React receive Paystack webhooks?
No. React runs in the browser and cannot accept incoming HTTP requests from Paystack. Webhooks are server-to-server. You need a backend server to receive, verify, and process Paystack webhooks.
What is the easiest backend to set up for Paystack webhooks if I use React?
If you want to stay in the JavaScript ecosystem, Node.js with Express is the simplest option. If you want frontend and backend in the same project, migrate to Next.js and use its API Routes or Route Handlers. For a serverless option, Firebase Cloud Functions or Supabase Edge Functions work well.
Can I verify Paystack payments from React without a backend?
No. The Paystack Verify Transaction API requires your secret key, which must never be exposed in frontend code. You need a backend to call the Verify API and to receive webhooks. Building a payment system without a backend is not secure.
How does my React app know when a Paystack webhook has been processed?
After the customer completes payment, your React app should poll your backend API to check the payment status. Your backend was updated by the webhook. Alternatively, use WebSockets, Server-Sent Events, or Supabase Realtime for instant updates.
Is the Paystack Popup onSuccess callback reliable for confirming payments?
No. The onSuccess callback runs in the browser and can be faked or missed due to network issues. Use it for optimistic UI updates (showing a spinner or success message), but always confirm payment status via the webhook on your backend before granting value.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon