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.

81 views

More from this blog

NishikantaRay

45 posts