Skip to content
RPA & Workflows 5 min read Published Sep 17, 2026

Razorpay Webhook Verification & Idempotency Guide

Manish Yadav
Contributor, VectorBloom · Enterprise Automation
Direct Answer (BLUF)
Executive Summary · ~50 words

To securely verify Razorpay webhooks and ensure idempotency, compute an HMAC SHA-256 hash using your configured webhook secret over the raw, unparsed request payload body. Compare it with the 'X-Razorpay-Signature' header. To handle Razorpay's 24-hour exponential retry policy safely, record each unique 'x-razorpay-event-id' (or entity ID + event status) in a database with unique constraints. Acknowledge with an immediate 2xx status code. Process subscription activation or credit provisioning asynchronously through a background queue.

Summarize with ChatGPT Perplexity Grok

Why do Razorpay webhooks fail for most Indian SaaS teams?

Everyone believes webhook integration is just writing a basic HTTP callback handler. Here’s why that’s wrong.

Engineering teams often hook Razorpay directly into downstream business logic. They update user balances, issue GST invoices, or grant workspace access right inside the HTTP request handler.

Under normal operating conditions, this naive architecture works. But payment flows in India frequently hit network latency, banking gateway drop-offs, and micro-outages. When an endpoint times out, throws an unhandled exception during heavy database loads, or hits transient infrastructure failures, webhooks pile up fast. If downstream systems run synchronously, slow response times immediately trigger delivery failures. That includes WhatsApp confirmation alerts or accounting syncs like those described in our Razorpay UPI & WhatsApp n8n Automation blueprint.

According to Webhooks – Best Practices – Razorpay, Razorpay considers any HTTP response outside the 2xx status range as an event delivery failure and retries delivery under an exponential backoff policy for 24 hours.

Without a dedicated idempotent ingestion layer, these retries execute repetitive database operations. You’ll end up with duplicate subscription provisioning, double credit allotments, and mismatched ledgers. If your endpoint consistently fails to return a 2xx status, Razorpay will automatically deactivate it.

Is this starting to feel like a lot?

Let us handle the busy work. LoomVector builds and ships this exact kind of system for Indian SMBs, end to end.

Talk to LoomVector

Step-by-Step Implementation: Verification and Idempotent Pipeline

Step 1: Capture the Raw HTTP Request Payload

Signature verification fails immediately if your framework deserializes the JSON request payload before computing the signature. Middleware like express.json() or standard body parsers alter byte sequences, whitespace, and Unicode formatting.

According to Razorpay Webhook Documentation Review | Svix Blog, Razorpay computes webhook signatures using an HMAC SHA-256 algorithm with your configured webhook secret as the cryptographic key and the raw request body as the message.

To preserve the raw body in an Express application, configure your route using express.raw():

import express from 'express';

const app = express();

app.post(
  '/api/v1/razorpay-webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    // req.body is an unmodified Buffer
  }
);

Step 2: Implement Cryptographic HMAC SHA-256 Signature Verification

Once you’ve isolated the raw buffer, extract the X-Razorpay-Signature header from the incoming request. Use Node.js’s native crypto module to generate an HMAC SHA-256 digest using your webhook secret. Perform a constant-time comparison using crypto.timingSafeEqual to eliminate timing attack vulnerabilities:

import crypto from 'crypto';

function verifyRazorpaySignature(rawBodyBuffer, receivedSignature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBodyBuffer)
    .digest('hex');

  const expectedBuffer = Buffer.from(expectedSignature, 'utf8');
  const receivedBuffer = Buffer.from(receivedSignature, 'utf8');

  if (expectedBuffer.length !== receivedBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
}

If the signature comparison returns false, terminate execution immediately and respond with an HTTP 400 Bad Request.

Step 3: Implement Idempotent Ingestion Using Event Identifiers

Razorpay guarantees at-least-once delivery. Network retries can send the exact same event multiple times.

According to Webhooks – Razorpay Curlec, incoming duplicate webhook deliveries can be identified using the ‘x-razorpay-event-id’ request header, whose value is unique per event, enabling idempotent handling.

To prevent duplicate execution, establish an idempotency_keys table in PostgreSQL:

CREATE TABLE webhook_events (
  event_id VARCHAR(255) PRIMARY KEY,
  status VARCHAR(50) NOT NULL,
  received_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

In your webhook controller, extract the header and attempt an atomic insert before invoking downstream business logic:

Incoming Webhook Request → Extract 'x-razorpay-event-id' → Database INSERT (Unique Check)
     ├─ Already Exists? → Return HTTP 200 OK (Stop Processing)
     └─ New Event? → Enqueue Background Worker → Return HTTP 200 OK
app.post('/api/v1/razorpay-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-razorpay-signature'];
  const eventId = req.headers['x-razorpay-event-id'];
  const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET;

  const isValid = verifyRazorpaySignature(req.body, signature, webhookSecret);
  if (!isValid) {
    return res.status(400).send('Invalid signature');
  }

  try {
    // Attempt an idempotent database write
    const isNewEvent = await db.insertEventId(eventId);
    if (!isNewEvent) {
      // Event has already been processed or is queued; acknowledge to halt retries
      return res.status(200).send('Event already processed');
    }

    // Parse payload buffer after verification
    const payload = JSON.parse(req.body.toString('utf8'));

    // Push event to background queue (e.g., BullMQ / SQS)
    await queue.add('process-payment', { eventId, data: payload });

    // Instantly acknowledge Razorpay
    return res.status(200).send('Accepted');
  } catch (err) {
    return res.status(500).send('Internal Server Error');
  }
});

According to Webhooks – Best Practices – Razorpay, Razorpay considers any HTTP response outside the 2xx status range as an event delivery failure and retries delivery under an exponential backoff policy for 24 hours. Acknowledging immediately with a 200 OK ensures your endpoint won’t trigger redundant retries.

Engineering teams scaling transaction-heavy payment backends often struggle with distributed state consistency, queuing architectures, and automated invoicing. LoomVector designs and builds production-grade ingestion systems, resilient microservice workflows, and payment infrastructure tailored for growing Indian software companies.

Sources

Checked on 18 Sep 2026 when this guide was researched:

  1. Razorpay Webhook Documentation Review | Svix Blog svix.com
  2. Webhooks – Best Practices – Razorpay razorpay.com
  3. Webhooks – Razorpay Curlec curlec.com

<!–[googlesitekit-end-of-content]–>

Key Takeaways
3 Points
  1. 01
    Verify Raw Buffers: Compute HMAC SHA-256 digests over untouched byte buffers using your webhook secret.
  2. 02
    De-duplicate with Event Headers: Use the unique x-razorpay-event-id header to record processed webhooks against database unique constraints.
  3. 03
    Decouple Queue from Ingestion: Return an HTTP 2xx response immediately after deduplication.
Get new posts by email

Direct implementation blueprints for Indian SMBs. One email per post, no spam.

By subscribing you agree to receive email updates and our Privacy Policy.

Advertisement
TEST AD SLOT
In-Article (Responsive)
Frequently Asked Questions
Share & Distribute Blueprint

Found this blueprint valuable?

Share with your engineering team, founder circles, or tech communities to accelerate deployment.

Was this helpful?

Free download

Razorpay Webhook Signature Verification and Idempotent Ingestion Workflow

A production-ready n8n workflow implementing raw payload capture, HMAC SHA-256 signature verification, and PostgreSQL idempotent deduplication using the x-razorpay-event-id header.

  • Webhook trigger configured to capture raw payload bodies and request headers
  • HMAC SHA-256 cryptographic verification using timing-safe comparison
  • PostgreSQL query to record and deduplicate incoming x-razorpay-event-id entries
  • Branching logic to acknowledge duplicates and queue new events with 200 OK responses

Manish Yadav
Manish Yadav Contributor, VectorBloom LinkedIn

Manish writes from running operations and business development across VectorBloom’s ventures, keeping every blueprint grounded in what actually works for running a services business day to day.

Need help implementing this blueprint in production?

LoomVector engineers custom web applications, direct WhatsApp Cloud API integrations, and self-hosted n8n enterprise workflows.

Talk to LoomVector
Link copied to clipboard

Get new posts by email

Direct implementation blueprints for Indian SMBs. No spam.

By subscribing you agree to receive email updates and our Privacy Policy.