A 32-person Pune SaaS firm took its accountant 6 hours every Monday to reconcile the prior week's Razorpay settlements against Zoho Books invoices. We replaced that with a 14-node n8n workflow that runs at 8 AM daily. The accountant now spends 20 minutes reviewing exceptions instead of keying journal entries by hand. This post gives you the exact node-by-node build, the credentials setup, the error handling, and the real ₹ cost to run it.
What does this n8n workflow actually do?
Every morning it pulls the previous day's Razorpay settlement using the settlement recon endpoint, splits the line items into payments, refunds, and adjustments, matches each payment to an open Zoho Books invoice by order notes, marks matched invoices as paid, and posts one consolidated journal entry for the net settlement amount and the Razorpay fee. Unmatched payments drop into a Slack channel for the accountant. Tested on n8n v1.62, July 2025.
Why automate Razorpay-to-Zoho recon now?
Razorpay settles on a T+2 cycle by default, and each settlement bundles dozens of payments minus refunds, minus the platform fee and 18% GST on that fee. Doing this by hand means an accountant cross-checks the settlement recon report against invoices line by line. We have built variants of this for three Indian SMBs since GST 2.0 landed on April 1, 2026, and the consistent win is the same: the fee-plus-GST journal entry, which humans routinely get wrong, becomes deterministic.
The 14 nodes, in order
Step 1: Set up the credentials
You need three credential sets in n8n. Razorpay uses HTTP Basic Auth with your key_id as username and key_secret as password — generate these under Settings → API Keys in the Razorpay Dashboard. Zoho Books uses OAuth2; register a Self Client in the Zoho API Console and grant the ZohoBooks.fullaccess.all scope. Slack uses a bot token with chat:write.
.in data centre. Your API base URL must be https://www.zohoapis.in/books/v3/, not .com. We lost 40 minutes to a 401 here on our first build because the OAuth token was minted on the wrong DC. The token domain and the API domain must match.
Step 2: The Razorpay settlement fetch node
Node 3 is an HTTP Request node. Razorpay's recon endpoint takes year, month, and day as query params and returns every transaction settled that day with Unix timestamps. Here is the node configuration:
{
"parameters": {
"method": "GET",
"url": "https://api.razorpay.com/v1/settlements/recon/combined",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendQuery": true,
"queryParameters": {
"parameters": [
{ "name": "year", "value": "={{ $json.year }}" },
{ "name": "month", "value": "={{ $json.month }}" },
{ "name": "day", "value": "={{ $json.day }}" },
{ "name": "count", "value": "100" }
]
}
},
"name": "Razorpay Settlement Recon",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
} The response items[] array holds objects with entity_id, type (payment, refund, transfer, adjustment), amount in paise, fee, tax, and settled_at. Verify this node by pinning a single day's output and counting items against the Razorpay Dashboard's settlement detail view — the count must match exactly before you wire anything downstream.
Step 3: Classify with a Switch node
Node 5 routes each item by its type field. Payments go to the matching branch; refunds and adjustments go straight to the journal builder because they do not map to a single invoice. The Switch node config:
{
"parameters": {
"dataType": "string",
"value1": "={{ $json.type }}",
"rules": {
"rules": [
{ "value2": "payment", "output": 0 },
{ "value2": "refund", "output": 1 },
{ "value2": "adjustment", "output": 2 }
]
},
"fallbackOutput": 3
},
"name": "Route By Type",
"type": "n8n-nodes-base.switch",
"typeVersion": 1
}Step 4: Match payments to Zoho invoices and post the journal
The match works on Razorpay payment notes. When you create the Razorpay payment link or order, write the Zoho invoice number into notes.invoice_no. Node 7 searches Zoho with that reference, node 9 marks it paid, and node 12 posts the consolidated journal. Razorpay's fee plus 18% GST becomes a debit to "Payment Gateway Charges" and "Input GST"; the net credit hits your bank clearing account. The journal POST body:
{
"parameters": {
"method": "POST",
"url": "https://www.zohoapis.in/books/v3/journals?organization_id={{ $env.ZOHO_ORG_ID }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "zohoOAuth2Api",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n journal_date: $json.date,\n reference_number: 'RZP-' + $json.settlement_id,\n notes: 'Auto-posted by n8n daily recon',\n line_items: [\n { account_id: $env.BANK_CLEARING_ID, debit_or_credit: 'debit', amount: $json.net_amount },\n { account_id: $env.PG_CHARGES_ID, debit_or_credit: 'debit', amount: $json.fee },\n { account_id: $env.INPUT_GST_ID, debit_or_credit: 'debit', amount: $json.tax },\n { account_id: $env.RAZORPAY_SUSPENSE, debit_or_credit: 'credit', amount: $json.gross_amount }\n ]\n}) }}"
},
"name": "Post Zoho Journal",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
}Zoho Books allows 100 requests per minute per organization, so a daily settlement with 80 payments stays well inside the limit. If you process more than 90 invoices in one run, add a Loop node with a 700ms wait between batches.
- Razorpay API key generated with read access to settlements
- Zoho OAuth2 token minted on the
.indata centre - Chart-of-accounts IDs stored as n8n environment variables
- Payment notes carry the Zoho invoice number
- Schedule Trigger set to 08:00 Asia/Kolkata
- Error Trigger workflow wired to Slack
- Single-day output verified against the Razorpay Dashboard
How do you handle errors in this workflow?
Three failure modes break this flow, and each needs its own guard. First, a payment with no matching invoice — node 8's IF sends these to a Slack channel with the payment ID and amount so the accountant decides manually. Second, a Zoho 401 from an expired token — n8n's OAuth2 credential auto-refreshes, but if the refresh token is revoked you get a hard fail, so attach an Error Trigger workflow that pings Slack. Third, a partial run where the journal posts but mark-as-paid fails — make the journal node the last write, and set reference_number to the settlement ID so a re-run is idempotent and Zoho rejects the duplicate.
reference_number to RZP-{settlement_id}. If the workflow re-runs for the same day, Zoho returns a duplicate-reference error instead of double-posting. We treat that specific error code as a success in the Error branch.
How much does it cost to run per month?
The only real cost is the box n8n runs on. Razorpay's API is free, Zoho Books API access is included in any paid plan, and Slack's free tier covers the alerts.
For one daily run, self-hosting on a Hetzner CX22 is the obvious pick. You would only move to n8n Cloud if you do not want to manage a server, which our AI and automation team handles for clients who prefer that.
When should you not automate this?
If you settle fewer than five payments a week, the manual route is fine — the workflow's maintenance cost outweighs 15 minutes of human checking. If your Razorpay account uses Route (split settlements to multiple linked accounts), this 14-node version will under-count, because Route settlements need the per-transfer settlement endpoint and a more complex match. And if your accountant does not trust auto-posted journals yet, run it in "draft journal" mode for a month first — Zoho lets you create journals as drafts that a human approves.
Real example: the Pune SaaS firm
The client sells a subscription product, so 90% of their Razorpay payments map cleanly to recurring invoices. Before automation, their accountant batched a week of settlements every Monday — 6 hours, and roughly two fee-GST entries wrong per month, each needing a correction journal. After we shipped this flow, month-end close moved from day 8 to day 4, and the fee-GST entries have been correct for six straight months because the math is now in a Code node, not a human's head. We saw the same close-time win building the lead-to-cash pipeline for Radiant Finance.
The journal entry for Razorpay's fee and GST was the part our accountant always got wrong. Making it deterministic was worth more than the time saved.
Frequently asked questions
Does this work with Razorpay Route or split settlements?
Not as-is. Route bundles transfers to linked accounts, so you need the instant-settlement payout endpoint and a per-transfer match. The 14-node version assumes a single settlement account. We extend it to Route on request.Can I post journals as drafts for review first?
Yes. Zoho Books accepts a journal in draft status that a human approves in the UI. We recommend running in draft mode for the first month, then flipping to auto-post once the entries reconcile cleanly.What happens if a payment has no matching invoice?
The IF node routes it to a Slack channel with the payment ID and amount. The accountant matches it manually. Common causes are manual payment links without an invoice number in the notes field.How do I stop double-posting if the workflow re-runs?
Set the journalreference_number to the Razorpay settlement ID. On a re-run, Zoho rejects the duplicate reference, and the Error branch treats that specific code as success. The flow is then idempotent.Does the Zoho rate limit cause problems?
Rarely. Zoho allows 100 requests per minute per organization. A daily settlement of 80 payments uses well under that. For 90-plus invoices, add a Loop node with a 700ms wait between batches.How long does it take to build?
We ship a working v1 in 5 to 7 working days, including credential setup, chart-of-accounts mapping, and a week of draft-mode review before go-live.Want this Razorpay-to-Zoho recon flow on your infra?
We ship a working, self-hosted n8n reconciliation workflow for Indian SMBs in 7 working days. Includes chart-of-accounts mapping and a draft-mode review period. Suitable if you run 50+ Razorpay settlements a month and close your books late. No slides — just your settlement data and our honest take.
Book a 20-min CallRelated reading
