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.
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.
Here is how a properly architected Paystack integration works when your frontend is React:
x-paystack-signature header using HMAC SHA512, processes the event (updates orders, credits accounts), and stores the result in your database.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.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
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.
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.
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).
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.
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.
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