A failed payment is not a lost customer — yet. Razorpay's own data shows automated retry systems recover 15-20% of failed transactions. We've shipped this n8n workflow for 4 D2C clients in the last 90 days, and the median recovery is ~12% of all drops within 24 hours. This post is the actual workflow — the trigger, the classifier, the message rules, the Sheets log — with the real Razorpay webhook payload and the n8n node config.
~12%
Median drops recovered within 24 hours (4 clients, Q1 2026)
70%
Customers abandon after a single payment failure (Razorpay data)
15-25%
Failures that are recoverable (insufficient funds, network)
~₹1,840/mo
Self-hosted n8n cost on Hetzner CX22 (tested May 2026)
## What this workflow does
In one sentence: it catches every Razorpay payment.failed webhook, classifies the failure into one of four buckets, fires the right recovery message via WhatsApp or email, and logs the outcome to Google Sheets so you can attribute revenue back to the workflow.
The four buckets matter because the message you send a customer whose card was declined is different from the one you send a customer whose UPI mandate timed out. Generic "your payment failed, try again" emails recover ~3%. Bucket-specific messages recover ~12%.
B1
Insufficient funds (recoverable)
Customer wants to buy. Bank says no. Send a 24-hour-delay WhatsApp nudge: "We'll hold your order for 24 hours — try again from your saved cart." Recovery rate: ~22%.
B2
Card declined / 3DS timeout (medium)
Often a network issue or expired card. Suggest UPI as alternative. Send within 2 minutes — the customer is still on the page. Recovery: ~14%.
B3
UPI mandate timeout (high)
Customer didn't approve the UPI request in their app within 5 minutes. Re-trigger a fresh UPI link via WhatsApp with a new collect request. Recovery: ~28%.
B4
Genuine fraud / hard decline (skip)
Stolen card flag, blocked merchant, suspicious geo. Don't message — you'll just spam fraud victims. Log silently for review.
## The Razorpay webhook payload (real)
This is what arrives at your n8n webhook URL when payment.failed fires. Trimmed for readability — the real payload includes a few more nested fields.
{
"entity": "event",
"account_id": "acc_DEMO12345abcd",
"event": "payment.failed",
"contains": ["payment"],
"payload": {
"payment": {
"entity": {
"id": "pay_OkqL3xR8nFvY7T",
"entity": "payment",
"amount": 89900,
"currency": "INR",
"status": "failed",
"order_id": "order_NjK4xR8nFvY7T",
"method": "upi",
"amount_refunded": 0,
"captured": false,
"description": "Order #INV-2026-04-1138",
"email": "rohit@example.com",
"contact": "+919876543210",
"notes": {
"cart_id": "cart_88291",
"customer_segment": "returning"
},
"fee": null,
"tax": null,
"error_code": "BAD_REQUEST_ERROR",
"error_description": "Payment failed due to UPI mandate timeout. Please try again.",
"error_source": "customer",
"error_step": "payment_authentication",
"error_reason": "payment_timed_out",
"vpa": "rohit@okhdfcbank",
"created_at": 1715162842
}
}
},
"created_at": 1715162844
}
The two fields you'll use heavily:
error_reason and
method. They drive the bucket classification.
## The n8n workflow — node by node
Total nodes: 7. Setup time on a fresh n8n install: ~45 minutes. Self-hosted on a Hetzner CX22 (₹740/mo for the VM + ~₹1,100/mo for managed Postgres = ₹1,840/mo as of May 2026).
1
Webhook Trigger node
Path: /razorpay-failed. Method: POST. Authentication: Header Auth (custom header X-Razorpay-Signature, validated in next node). Response Mode: "Respond Immediately" with status 200 — Razorpay retries on non-2xx. Copy the resulting webhook URL into Razorpay Dashboard > Settings > Webhooks > Create New, with the event "payment.failed" subscribed.
2
Code node — verify signature
Razorpay signs every webhook with HMAC-SHA256 over the raw body, using your webhook secret. In a Code (JS) node: const crypto = require('crypto'); const expected = crypto.createHmac('sha256', $env.RAZORPAY_WEBHOOK_SECRET).update(JSON.stringify($json)).digest('hex'); return expected === $headers['x-razorpay-signature'] ? [{json: $json}] : []. If this returns [], the workflow stops and the request is treated as forgery.
3
Switch node — classify failure bucket
4 outputs, one per bucket. Routing rules on payload.payment.entity.error_reason and method. Output 1 (B1 — insufficient funds): error_reason in ["insufficient_funds", "low_balance"]. Output 2 (B2 — card decline): method == "card" AND error_reason in ["payment_failed", "3ds_failed"]. Output 3 (B3 — UPI timeout): method == "upi" AND error_reason in ["payment_timed_out", "mandate_failed"]. Output 4 (B4 — fraud/hard): error_source == "issuer_bank" AND error_step == "fraud_check".
4
Wait node (per bucket)
B1 wait: 1 hour. B2 wait: 2 minutes (customer might still be at checkout). B3 wait: immediate. B4 wait: skip — go straight to log. The wait timer holds the workflow execution; n8n's queue mode handles thousands of concurrent waits comfortably on the CX22.
5
HTTP Request node — send WhatsApp
POST to your WhatsApp BSP's send-message endpoint (we use AiSensy / Wati / Gupshup depending on client). Use a pre-approved utility template per bucket — utility messages cost ~₹0.115 each, marketing would be ~₹0.86. Template variables: customer name, order ID, retry link. Retry link is your checkout URL with ?retry_payment_id={{$json.payload.payment.entity.id}}.
6
HTTP Request node — fallback email
Run in parallel to the WhatsApp branch. Some customers don't have WhatsApp tied to the same number — email reaches them too. Use Resend, Postmark, or Amazon SES. Subject line per bucket: "Your order is on hold — retry from your cart" (B1), "Try paying with UPI instead?" (B2), etc.
7
Google Sheets node — log row
Append a row to a "Payment Recovery Log" sheet. Columns: timestamp, payment_id, order_id, customer_email, customer_phone, amount, bucket, error_reason, message_sent_via (whatsapp/email/none), recovery_status (initial value "pending"). A daily Cron node flips recovery_status to "recovered" for any payment_id that later succeeds, by querying Razorpay's Orders API.
## The attribution loop (often skipped)
Without attribution, you can't tell whether the workflow is helping. We add a daily 9am Cron in n8n that:
1. Reads all rows in the log with status "pending" and timestamp older than 24 hours.
2. For each, queries GET https://api.razorpay.com/v1/orders/{order_id}/payments.
3. If any payment for that order is now "captured", marks the row as "recovered" with the new payment ID.
4. Else marks as "lost".
5. Writes a daily summary to a separate sheet: total drops, recovered by bucket, recovery rate, recovered ₹ value.
After 30 days you have real numbers, not vibes. For our 4 clients, the steady state is 11-14% recovery on bucket B1+B2+B3 combined (B4 is excluded since we don't message).
## When NOT to use this workflow
Three cases where automated recovery hurts more than it helps:
-
B2C low-AOV with high WhatsApp opt-in fatigue. If your customers are getting 5+ marketing messages a week from you, another "your payment failed" message annoys them and lifts unsubscribes. Use email-only.
-
Subscription products. Razorpay Subscriptions has its own retry logic that's tuned for recurring billing. Don't double-retry — you'll trigger card-velocity blocks at the issuer.
-
Buyer's bank is repeatedly hard-declining. If the same customer's card has hard-declined 3 times in 7 days, stop messaging. The bank has likely blocked your merchant category code (MCC). Your fraud-tooling vendor or CS team should take over.
## Real example — a Bangalore D2C personal-care brand, ₹1.4 Cr GMV/month
Client: a 5-person D2C team running on Shopify with Razorpay. AOV ₹620. Monthly drops: ~3,400 failed payments worth ~₹21 lakh GMV.
Before: their existing recovery was a 24-hour-delay generic email. Recovery rate: ~3.1%, ~₹65k recovered/month.
After we shipped the n8n workflow (3 working days, ₹48k fixed fee + the ₹1,840/mo infra):
- B1 (insufficient funds): 22% recovery, ~₹1.6 lakh/month recovered.
- B2 (card decline): 14% recovery, ~₹85k/month.
- B3 (UPI timeout): 28% recovery, ~₹1.1 lakh/month.
- B4 (fraud): zero, by design.
Weighted recovery: 11.7% of drops. Recovered GMV: ~₹2.45 lakh/month. Net of WhatsApp template costs (~₹4,200/mo) and email costs (~₹600/mo), the workflow returns ~₹2.4 lakh/month. Payback on the build: 6 days.
The Reddit thread on [r/IndiaBusiness](https://www.reddit.com/r/IndiaBusiness/) where Indian merchants compare their recovery setups is worth scanning — most are still on generic-email recovery and leaving money on the table.
## A note on PenLeap as an internal case study
We use a similar n8n workflow on [PenLeap](https://penleap.com), our in-house edtech product. PenLeap parents who try to upgrade to a paid plan and hit a UPI mandate timeout get a re-collect link via WhatsApp within 30 seconds. Our internal recovery rate on UPI timeouts ran ~31% in March 2026 — slightly higher than client averages, likely because the buying intent is stronger (mid-funnel parent, not impulse purchase).
## Variations worth shipping
Once the base workflow is live, three extensions usually pay for themselves:
-
Slack alert on B4 spikes. If B4 (fraud) crosses a daily threshold, ping #payments. Could mean a card-testing attack on your checkout.
-
Discount fallback for B1 after 24 hours. If the customer hasn't recovered by 24h, send a 5% off code via email. Recovery on this branch: ~7% additional. Net of margin, still positive on most products.
-
Customer-segment branching. Returning customers get a different message ("we noticed your payment didn't go through — here's the link") than first-time buyers ("your card was declined; try UPI?"). Adds 1-2 percentage points to recovery.
WhatsApp utility-template approval is non-trivial: Meta's review team is stricter on what qualifies as utility vs marketing. A "your payment failed, retry here" message must clearly tie to a customer-initiated transaction — include the order ID and the product name in the template body. Generic re-engagement language ("we miss you, complete your order") gets categorised as marketing, charged at ~7.5x the utility rate. Get the template approved as utility from day one; reclassification later is painful.
## Bucket-by-bucket recovery rates we have measured
| Bucket |
Trigger condition |
Median recovery rate (4 clients, Q1 2026) |
Best message channel |
| B1 — insufficient funds |
error_reason in [insufficient_funds, low_balance] |
~22% within 24h |
WhatsApp utility, 1h delay |
| B2 — card decline / 3DS |
method=card AND error_reason in [payment_failed, 3ds_failed] |
~14% within 24h |
WhatsApp utility + email, 2min delay |
| B3 — UPI mandate timeout |
method=upi AND error_reason in [payment_timed_out, mandate_failed] |
~28% within 24h |
WhatsApp utility, immediate |
| B4 — fraud / hard decline |
error_source=issuer_bank AND error_step=fraud_check |
0% (silent log only) |
None — do not message |
## Pre-launch checklist for the workflow
- Razorpay webhook secret stored as env var, not hardcoded
- HMAC-SHA256 signature verification node tested with both valid and invalid payloads
- WhatsApp utility templates pre-approved by Meta for all 3 buckets
- Sheets log columns include payment_id, captured_at, bucket, recovery_status
- Daily cron set to flip recovery_status from "pending" to "recovered" / "lost"
- Postgres dedupe (or Sheets check) on payment_id before WhatsApp send
- n8n WEBHOOK_URL env var set to public URL, not localhost
- Slack channel / dashboard set up for weekly recovery summary
## How to set up the Razorpay webhook on the dashboard
People ask this often. The Razorpay-side setup takes about 6 minutes:
1. Login to the Razorpay Dashboard (Live mode, not Test).
2. Navigate to Settings > Webhooks.
3. Click Create New Webhook.
4. Webhook URL: paste the URL from your n8n Webhook Trigger node (e.g., https://n8n.yourdomain.com/webhook/razorpay-failed).
5. Active Events: tick payment.failed. Optionally also tick payment.captured if you want to log successes for the same workflow.
6. Generate a webhook secret (32+ characters, random). Save it as an env var on your n8n instance — RAZORPAY_WEBHOOK_SECRET.
7. Save the webhook.
Razorpay sends a test ping when you save. Your n8n workflow should fire and write a row to Google Sheets. If nothing happens, check the Razorpay dashboard webhook logs first — they'll show 4xx/5xx responses from your side.
## Common operational mistakes
Three things that have burnt our clients' workflows in the first two weeks of running:
-
Webhook fires but workflow doesn't trigger. Almost always n8n's queue mode misconfigured. Check that the n8n process has WEBHOOK_URL set to your public URL, not localhost.
-
Sheets logs duplicates. Razorpay's at-least-once delivery means duplicate webhooks. Add a Postgres dedupe (or a simple Sheets-based check by payment_id) before the append step.
-
WhatsApp template gets reclassified mid-run. Meta sometimes recategorises a previously-utility template as marketing. The workflow keeps running but suddenly each send costs 7.5x. Add a daily check on template status via the Meta Graph API.
## How this scales
At 50,000 events/month (typical for a ₹2 Cr GMV D2C), the workflow runs comfortably on the same Hetzner CX22 we mentioned. CPU stays below 30%; memory below 1.2GB. Postgres growth is ~80MB/month for the queue tables.
Above 200,000 events/month, you'll want to either upgrade the VM (CX32 at ~₹1,400/mo, doubles the resources) or move heavy classification logic to a dedicated worker (Bull queue, separate Node service). At our largest D2C client running this pattern, monthly events exceed 600,000 and the workflow runs on a dedicated CPX31 (~₹2,100/mo).
The decision on when to upgrade is usually obvious from execution times in n8n's logs — if average execution time creeps above 2 seconds for a workflow that should run in 200ms, it's time.
## FAQ
### Where do I find the error_reason values?
Razorpay's [error codes documentation](https://razorpay.com/docs/errors/) lists every error_code, error_reason, and error_source combination. The list grows; check it quarterly. Most production failures fall into ~12 distinct error_reasons.
### Why use WhatsApp utility template, not marketing?
Cost. Utility templates cost ~₹0.115 per message in India versus ~₹0.86 for marketing. A "your payment didn't go through, retry here" message qualifies as utility (it's transactional, follow-up to a customer-initiated action) under Meta's content policy. Get the template approved as utility, not marketing, for the lower rate.
### Will Razorpay retry the webhook if my n8n is down?
Yes. Razorpay follows at-least-once delivery — it retries on any non-2xx response with exponential backoff for up to 24 hours. Make sure your webhook node responds with 200 quickly even if downstream nodes fail; otherwise you'll get duplicate sends.
### Can I run this on n8n Cloud instead of self-hosted?
Yes. n8n Cloud's Pro plan (~₹1,650/mo at current INR conversion) handles ~10k executions/month — fine for most D2C SMBs. We default to self-hosted for clients above ~30k executions/month or who want their data inside their own VPC.
### How do I prevent duplicate recovery messages?
Add a Postgres node before the WhatsApp send: SELECT 1 FROM payment_recovery_log WHERE payment_id = $1 AND created_at > now() - interval '7 days'. If the row exists, skip the send. Razorpay sometimes fires payment.failed twice for the same payment — this dedupes.
### What about UPI mandate failures specifically?
UPI mandate failures (recurring UPI not approved by customer) are the highest-recovery bucket because the customer's intent is strong — they had set up the mandate and just missed the in-app prompt. Re-trigger via Razorpay's Create Order with method=upi, send the new link in WhatsApp, recovery rates cluster around 25-35%.
### Can I use Pabbly Connect or Zapier instead of n8n?
Yes, with caveats. Pabbly is cheaper but lacks the Code node flexibility for HMAC verification and complex routing. Zapier is more polished but ~3-5x the cost at this volume. For Indian SMBs running 5k-50k executions/month, n8n self-hosted is the sweet spot.
Want This Payment-Recovery Flow for Your Business?
We ship this n8n + Razorpay workflow end-to-end in 5 working days — webhook setup, signature verification, bucket classification, WhatsApp BSP integration, Sheets attribution, and a 30-day post-launch review. Typical scope: ₹45,000 fixed fee + ₹1,840/mo for self-hosted n8n. Suitable for D2C, edtech, marketplaces with ₹50 lakh+ GMV/month.
Book a 20-min Workflow Review
If you want the exact n8n JSON export for the workflow, drop a mail to contact@softechinfra.com — we'll send it (with placeholder credentials) along with the WhatsApp template language we've had approved as utility.