Skip to main content

Command Palette

Search for a command to run...

💳 Razorpay Integration with React and Node.js — Including Webhook Verification

Updated
4 min read
💳 Razorpay Integration with React and Node.js — Including Webhook Verification

Want to collect payments securely in your web app? Razorpay offers a clean, developer-friendly API for seamless integration. In this blog, we’ll walk through how to integrate Razorpay with a React frontend and Node.js backend — and importantly, how to securely verify payments using webhooks.


🔧 Prerequisites

Before we dive in, make sure you have:

  • A Razorpay account

  • Created your API Key (Key ID & Secret)

  • Setup your webhook secret from Razorpay Dashboard

  • A basic React frontend and Node.js Express backend

  • (Optional) A database like MongoDB for storing bookings or orders


🔷 Step 1: Setup Razorpay Credentials

Create a .env file on your backend:

RAZORPAY_KEY_ID=your_key_id
RAZORPAY_KEY_SECRET=your_secret_key
RAZORPAY_WEBHOOK_SECRET=your_webhook_secret

In your React project, also add:

REACT_APP_RAZORPAY_KEY_ID=your_key_id

⚛️ Step 2: Create a Payment Button in React

// components/PaymentButton.js
import React from "react";
import axios from "axios";

const PaymentButton = ({ slotData }) => {
  const handlePayment = async () => {
    try {
      const { data } = await axios.post("/api/v1/slots/order", {
        amount: slotData.price,
      });

      const options = {
        key: process.env.REACT_APP_RAZORPAY_KEY_ID,
        amount: data.amount,
        currency: "INR",
        order_id: data.order_id,
        name: "Course Booking",
        description: "Slot Booking Payment",
        handler: function (response) {
          alert("Payment successful! We’ll update you shortly.");
          // Note: Do not trust only frontend. Use webhook.
        },
        prefill: {
          name: slotData.name,
          email: slotData.email,
          contact: slotData.phone,
        },
        theme: {
          color: "#3399cc",
        },
      };

      const razorpay = new window.Razorpay(options);
      razorpay.open();
    } catch (err) {
      console.error("Error initiating payment", err);
    }
  };

  return <button onClick={handlePayment}>Pay Now</button>;
};

export default PaymentButton;

🧠 Step 3: Create Razorpay Order in Node.js Backend

// routes/slots.js
import express from "express";
import Razorpay from "razorpay";

const router = express.Router();

const razorpay = new Razorpay({
  key_id: process.env.RAZORPAY_KEY_ID,
  key_secret: process.env.RAZORPAY_KEY_SECRET,
});

router.post("/slots/order", createOrder, async (req, res) => {
  try {
    const order = await Razorpay.orders.create({
      amount: req.validatedBody.amount * 100,
      currency: "INR",
      receipt: `receipt_${Date.now()}`,
    });

    const newSlot = new Slot({
      order_id: order.id,
      price: req.validatedBody.amount,
      payment_status: "pending",
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    await newSlot.save();

    return res.status(200).json({
      order_id: order.id,
      currency: order.currency,
      amount: order.amount,
    });
  } catch (err) {
    console.error("Error during payment:", err);
    return res.status(500).json({ status: "failed", error: err.message });
  }
});

📡 Step 4: Setup Razorpay Webhook on Backend

This is where the magic of secure verification happens.

🧩 Update your middleware in app.js or server.js:

import express from "express";
const app = express();

app.use("/slots/webhook", express.raw({ type: "application/json" })); // before express.json()
app.use(express.json());
app.use(
  bodyParser.json({
    verify: (req, res, buf) => {
      req.rawBody = buf;
    },
  })
);

🔐 Handle Webhook Route

// routes/slots.js
import crypto from "crypto";

async function validateWebhookSignature(payload, signature, secret) {
  const generatedSignature = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");
  return generatedSignature === signature;
}

router.post("/slots/webhook", async (req, res) => {
  try {
    const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET;
    if (!webhookSecret) {
      return res.status(500).json({ message: "Webhook secret missing" });
    }

    const receivedSignature = req.headers["x-razorpay-signature"];
    let isValid = await validateWebhookSignature(
      JSON.stringify(JSON.parse(req.rawBody)),
      receivedSignature,
      webhookSecret
    );

    if (!isValid) {
      return res.status(400).json({ message: "Invalid Signature" });
    }
    const event = req.body.event;

    if (event === "payment.captured") {
      const { order_id, id, amount, status, method, email } =
        req.body.payload.payment.entity;

      await Slot.findOneAndUpdate(
        { order_id },
        {
          payment_status: "captured",
          payment_method: method,
          email: email,
          payment_id: id,
          updatedAt: new Date(),
          isBooked: true,
        }
      );

      res
        .status(200)
        .json({ message: "Payment Captured & Updated Successfully" });
    } else {
      console.warn(`Unhandled Webhook Event: ${event}`);
      res.status(400).json({ message: "Unhandled Webhook Event" });
    }
  } catch (err) {
    console.error("Error in webhook", err);
    res.status(500).json({ message: "Internal Server Error" });
  }
});

🧪 Step 5: Test Razorpay Webhooks Locally

Since Razorpay can't reach localhost, use ngrok to expose your local server:

npx ngrok http 5000

Then set https://your-ngrok-url/slots/webhook as your webhook URL in Razorpay Dashboard and select payment.captured as the event.


🧠 What I Learned from Debugging

  • Signature mismatch? Almost always caused by express.json() parsing the body before validation. Use express.raw() for webhook route.

  • Never rely only on Razorpay's frontend handler — users could manipulate it. Webhooks are your source of truth.

  • You can also log webhook payloads during testing and compare them with your expected values.


✅ Summary

PartTech / Tool
UI ButtonReact + Razorpay JS SDK
Order CreationNode.js (Express)
Payment ModalRazorpay Checkout
VerificationRazorpay Webhook
DB UpdateMongoDB / Mongoose

🚀 Bonus Tip

To secure even further:

  • Save order and user details during order creation.

  • On webhook, cross-check the payment details and compare with stored data before updating DB.

  • Use try/catch blocks and log failures for debugging real production issues.


🔚 Final Thoughts

Building your own payment flow is powerful and surprisingly straightforward. Razorpay’s developer tools make it smooth, but real-time verification is the key to trust and reliability in your system. With this setup, your app can handle payments and bookings securely, scalably, and professionally.