# 💳 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:

```bash
RAZORPAY_KEY_ID=your_key_id
RAZORPAY_KEY_SECRET=your_secret_key
RAZORPAY_WEBHOOK_SECRET=your_webhook_secret
```

In your React project, also add:

```bash
REACT_APP_RAZORPAY_KEY_ID=your_key_id
```

---

## ⚛️ Step 2: Create a Payment Button in React

```javascript
// 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

```javascript
// 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`:

```javascript
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

```javascript
// 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](https://ngrok.com/) to expose your local server:

```bash
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

| Part | Tech / Tool |
| --- | --- |
| UI Button | React + Razorpay JS SDK |
| Order Creation | Node.js (Express) |
| Payment Modal | Razorpay Checkout |
| Verification | Razorpay Webhook |
| DB Update | MongoDB / 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.
