Razorpay Subscription Integration Guide for SaaS (with Real Indie Dev Pain & Code)
Complete Razorpay subscription setup for your SaaS—step-by-step flow, API links, code samples, and what Razorpay won’t tell you upfront.

🧠 Why This Exists
Let me paint you a picture:
I was supposed to launch my product PitchMirror on June 1st. Like any good indie dev, I prepped, coded, designed, did the whole nine yards. Then came payment integration. I thought, "Let’s go Stripe, smooth dev experience, right?"
I applied for a live key on May 22nd.
Spoiler: After a back-and-forth email thread, they pointed me to this link: Stripe India invite-only status. Yeah. Oof.
Meanwhile, Razorpay? I registered on May 29th, got a call the next day, fixed a few website issues they mentioned, and boom—verified within 24 hours. Fast. Friendly. Very little headache.
💡 Why This Guide Will Save You
So once I got verified, I jumped into Razorpay’s docs to integrate subscriptions.
The docs are technically clear. But they’re scattered AF.
You need to bounce between 3–5 pages just to wire up one flow. Trial and error all the way. That’s why I wrote this: so you don’t spend your launch weekend rage-scrolling their docs like I did.
💳 One-Time Payment Integration (a.k.a. the Warm-Up)
Checkout Docs: https://razorpay.com/docs/payments/payment-gateway/web-integration/standard/integration-steps
🔄 Flow Diagram

This covers one-time payments. Simple. Clean. Fast. But for subscriptions? Buckle up.
🔁 Razorpay Subscriptions: Everything You Need to Know
⚙️ Step 0: Create a Subscription Plan
This is a one-time setup in your dashboard, not something you do per user.
- Go to your Razorpay Dashboard > Subscriptions
- Click “Create Plan” (monthly, yearly, etc.)
- Give it a name, billing cycle, price, and tax info
- Save it and copy the
plan_id
- Store it in your
.env
file so you can change plans later without changing code
You only need to do this once unless you're rolling out multiple tiers or pricing models.
📦 Real Flow Begins Here (When User Clicks Subscribe)
This is the point when a user in your app clicks "Subscribe Now". Everything after this is dynamic.
🔄 Subscription Flow Diagram

✅ Step 1: Create a Customer (if needed)
Before starting the subscription flow, check if the user already has a customer_id
stored in your database.
If not, create a new customer:
API Docs: https://razorpay.com/docs/api/customers/create/
POST /v1/customers
{
"name": "Sourav Layek",
"email": "[email protected]",
"contact": "9876543210",
"notes": {
"user_id": "user_1234"
}
}
Store the returned customer_id
for future transactions.
✅ Step 2: Create a Subscription
API Docs: https://razorpay.com/docs/api/payments/subscriptions/create-subscription/
POST /v1/subscriptions
{
"plan_id": "plan_1234xyz",
"customer_notify": 0,
"total_count": 12,
"quantity": 1,
"customer_id": "cust_123",
"notes": {
"user_id": "user_1234"
}
}
🔍 Key Fields Explained:
plan_id
: From Step 0total_count
: Number of times to charge (e.g., 12 = monthly for 1 year)quantity
: Keep it 1 unless selling bulk planscustomer_notify
: Set to 0 if you want to control emailsnotes
: Useful to store your internal user ID
Once you have the subscription_id
, send it to your frontend.
✅ Step 3: Razorpay Checkout
Use the same Razorpay Checkout flow, but pass in the subscription_id
instead of an order_id
.
const options = {
key: 'rzp_test_key',
subscription_id: 'sub_123xyz',
customer_id: 'cust_123',
name: 'YourAppName',
handler: function (response) {
// Verify and update DB
},
prefill: {
name: 'Sourav Layek',
email: '[email protected]',
contact: '9876543210',
},
theme: { color: '#FF6500' }
};
✅ Step 4: Webhooks (A Must!)
Webhook Event Docs: https://razorpay.com/docs/payments/subscriptions/subscribe-to-webhooks/#webhook-events-and-descriptions
Recommended Events (and What to Do With Them):
subscription.activated
:- When a subscription is successfully activated (after the first payment or retry success).
- ✅ Enable access to paid features immediately.
subscription.charged
:- Triggered on each successful recurring charge.
- 🔄 Reset quotas or monthly counters.
subscription.pending
:- Payment failed but Razorpay will retry.
- ⚠️ Notify user gently: “Hey, your payment didn’t go through. Razorpay will retry it.”
subscription.halted
:- Payment failed and retry attempts exhausted.
- 🚨 Alert the user: “Your subscription is halted. Please update your payment method.”
subscription.completed
:- All planned billing cycles are completed.
- 🔒 Lock paid features, prompt user to renew.
subscription.cancelled
:- User manually cancelled subscription.
- 🚫 Disable premium features immediately or at the end of current billing cycle.
Payload Format: https://razorpay.com/docs/webhooks/payloads/subscriptions/
Payload contains this useful structure:
{
"payload": {
"subscription": {
"entity": {
"id": "sub_123",
"customer_id": "cust_123",
"current_start": 1710000000,
"current_end": 1712592000,
"plan_id": "plan_123",
"notes": {
"user_id": "user_123"
}
}
}
}
}
Use this to update:
- Subscription status
- Validity window (start & end)
- Access rights in your DB
Thinking how to test your webhook locally while developing check this out (https://blog.souravlayek.com/the-hidden-stripe-debugging-hack-every-developer-should-know/) this works for razorpay too.
🧰 Tips & Final Thoughts
- Store everything you can in
notes
—it saves you later. - Razorpay UPI support is smooth; subscriptions work on most methods.
- If your webhook returns
2xx
, Razorpay won’t retry. Log carefully. - Use
.env
for keys/plan IDs to keep code portable.
⚠️ Downside of Razorpay
While Razorpay works like a charm for Indian payments (especially UPI and INR subscriptions), there are some caveats:
- ❌ Limited international payment support (especially card-based recurring charges for users outside India).
- 🚫 Stripe-like developer ergonomics are missing—there’s more boilerplate, less polish.
That said, it’s still the best bet for fast onboarding if your business is India-first.
I’ll soon be writing a blog on how to harness both Razorpay and Stripe together in your app to get the best of both worlds. Subscribe now to get it in your inbox. ⚡
🧪 Curious what product I was building?
Let me quickly plug it in:
🎤 PitchMirror
PitchMirror helps professionals improve their spoken English by analyzing their speech using AI. Just upload an audio or paste a meeting link — PitchMirror gives you instant feedback on filler words, pacing, tone, and clarity. You’ll get a personalized confidence score, tone analysis, and actionable tips to sound better in interviews, meetings, or pitches.
It’s like having a speech coach in your pocket—friendly, fast, and judgment-free.
📎 All the Links
- Standard Checkout Integration
- Subscription Plan Setup
- Create Customer API
- Create Subscription API
- Webhook Events
- Webhook Payload Format
"Indie dev tip: When Stripe leaves you on read, Razorpay picks up the phone."
Hope this helps! Now go ship that SaaS 🚀