| name | go-live |
| description | Production-ready checklist and security hardening for Razorpay integration — rate limiting, error monitoring, compliance. Use when the user asks to "launch to production", "go live", "run the security checklist", "harden my integration", or is preparing a Razorpay integration for real customers. |
| argument-hint | [checklist|security|monitoring] |
Go-Live Checklist & Production Hardening
Use this guide before launching a Razorpay integration to production, or to harden an existing live integration.
1. Pre-Launch Checklist
Complete every item before going live:
2. Security Hardening
a. Rate Limiting the Webhook Endpoint
const WINDOW_MS = 60_000;
const MAX_REQUESTS = 100;
const requestCounts = new Map<string, { count: number; resetAt: number }>();
function isRateLimited(ip: string): boolean {
const now = Date.now();
const entry = requestCounts.get(ip);
if (!entry || now > entry.resetAt) {
requestCounts.set(ip, { count: 1, resetAt: now + WINDOW_MS });
return false;
}
entry.count++;
return entry.count > MAX_REQUESTS;
}
export async function POST(req: Request) {
const ip = req.headers.get("x-forwarded-for") ?? "unknown";
if (isRateLimited(ip)) {
return new Response("Too Many Requests", { status: 429 });
}
}
Note: For production at scale, use a distributed rate limiter (Redis-based) instead of in-memory.
b. Validate Webhook Source
Signature verification is sufficient for authenticity. Additionally:
- Check
Content-Type is application/json and reject non-JSON requests
- Return
400 early for malformed payloads before attempting signature verification
c. Never Expose API Secret Client-Side
- Only
NEXT_PUBLIC_RAZORPAY_KEY_ID should be public
RAZORPAY_KEY_SECRET and RAZORPAY_WEBHOOK_SECRET are server-only
- Never prefix secrets with
NEXT_PUBLIC_ in Next.js
d. API Route Protection
- All billing routes (except webhook) must require authentication
- Webhook route must be exempt from auth middleware but must verify signature
- Use CSRF protection on billing mutation routes (plan changes, cancellations)
e. Input Validation
- Validate
planKey against allowed values — do not trust client input
- Validate amounts server-side for one-time payments
- Sanitize user input in
notes fields before sending to Razorpay
3. Environment Variable Management
# .env.local (development)
RAZORPAY_KEY_ID=rzp_test_xxx
RAZORPAY_KEY_SECRET=test_secret
NEXT_PUBLIC_RAZORPAY_KEY_ID=rzp_test_xxx
RAZORPAY_WEBHOOK_SECRET=test_webhook_secret
# Production (Vercel/Railway/etc)
RAZORPAY_KEY_ID=rzp_live_xxx
RAZORPAY_KEY_SECRET=live_secret
NEXT_PUBLIC_RAZORPAY_KEY_ID=rzp_live_xxx
RAZORPAY_WEBHOOK_SECRET=live_webhook_secret
- Never commit
.env files — ensure .env* is in .gitignore
- Use platform secrets management (Vercel env vars, Railway variables, AWS Secrets Manager, etc.)
- Rotate webhook secret periodically — update in both Razorpay Dashboard and your platform
- Use separate API key pairs for test and live modes
4. Error Monitoring Setup
Set up alerts for these critical scenarios:
- Webhook processing errors: Catch and report failures in event handlers
- Signature verification failures: Alert immediately — may indicate tampering
- Elevated payment failure rates: Monitor
payment.failed event frequency
- Webhook response times: Must respond within 5 seconds (Razorpay timeout)
- Subscription status distribution: Track active vs churned vs paused ratios
import * as Sentry from "@sentry/nextjs";
try {
await processWebhookEvent(event);
} catch (error) {
Sentry.captureException(error, {
tags: {
event_type: event.event,
subscription_id: event.payload?.subscription?.entity?.id,
},
});
}
5. Logging Best Practices
- Do log: Webhook event type, subscription ID, payment ID (for debugging)
- Never log: Full payment details, card numbers, API secrets, webhook secrets
- Log signature verification failures with request metadata (IP, headers) for investigation
- Use structured logging (JSON format) for easy filtering and alerting
console.log(JSON.stringify({
event: "webhook.received",
type: payload.event,
subscriptionId: payload.payload?.subscription?.entity?.id,
timestamp: new Date().toISOString(),
}));
console.log("Webhook payload:", JSON.stringify(payload));
console.log("Secret:", process.env.RAZORPAY_WEBHOOK_SECRET);
6. Database Considerations
- Index properly: Add indexes on
user_id, razorpay_subscription_id, and razorpay_customer_id columns
- Set up database backups before launch — test restore process
- Consider read replicas if webhook volume is high (thousands of events/minute)
- Add database connection pooling — webhooks create concurrent connections
CREATE INDEX idx_subscriptions_user_id ON subscriptions(user_id);
CREATE INDEX idx_subscriptions_razorpay_id ON subscriptions(razorpay_subscription_id);
CREATE INDEX idx_payments_subscription_id ON payments(subscription_id);
CREATE INDEX idx_payments_razorpay_id ON payments(razorpay_payment_id);
7. Webhook Reliability
- Return 200 within 5 seconds — Razorpay times out after that
- For slow operations, acknowledge immediately and process async (queue pattern):
export async function POST(req: Request) {
const isValid = verifySignature(body, signature);
if (!isValid) return new Response("Invalid", { status: 400 });
await queue.add("process-webhook", { event: body });
return new Response("OK", { status: 200 });
}
- Set up a dead letter queue for failed webhook processing
- Monitor for missing webhooks: Compare Razorpay Dashboard event count vs your database records
- Razorpay retries failed webhooks — ensure your handler is idempotent
8. Scaling Considerations
- Webhook endpoint should be stateless — no in-memory state between requests
- Use database-level locking (optimistic locking with version columns) not in-memory locks
- Consider a separate webhook worker if processing is heavy — decouple ingestion from processing
- Razorpay may send bursts of webhooks (e.g., batch subscription renewals) — handle concurrency gracefully
- Use database transactions to prevent race conditions when updating subscription state
9. Compliance
- GST invoices required for Indian businesses
- Store payment records for minimum 8 years (Indian tax law)
- PCI compliance: Never store card details — Razorpay handles tokenization and PCI-DSS
- Display pricing inclusive of GST on your website
- Include SAC code 998314 for SaaS services in invoices
- Provide clear cancellation and refund policies on your website