Stripe + Razorpay vs. Airtable Revenue Ledger: Nightly Reconciliation with Anomaly Alerts to Slack via n8n
Catch revenue discrepancies between your payment gateways and your internal ledger before your accountant does — automatically, every night.
The stack in the order it runs — data flows from the source through to where it lands.
If you're running Stripe for international and Razorpay for domestic India, you have a reconciliation gap. Charges captured in one gateway don't always match what your finance team recorded. Monthly manual reconciliation means you find errors weeks after they happened. The real pain isn't fraud — it's mundane: failed webhook retries, partial refunds logged on one side only, currency conversion rounding, settlement timing mismatches that silently inflate or deflate reported revenue.
n8n is the right engine because it handles multi-branch conditional logic natively with no per-task pricing. You need one branch for Stripe, one for Razorpay, a merge node to compare, and a conditional to route mismatches. Zapier charges you a separate Zap per branch and can't do the comparison step inline. Make can do it, but its data-store module is clunky for row-level diffing. Airtable as the ledger gives you a queryable, human-editable source of truth your finance team can open without an API key.
The tradeoff is self-hosting n8n or paying for n8n Cloud. If your team has zero DevOps tolerance, use Make and accept that you'll need a formula column in Airtable to do the diff logic instead of doing it in the workflow. The Slack alert is intentionally simple — a single message with a mismatch count and a link to the filtered Airtable view — so the on-call person knows exactly what to click.
Don't use this if you already have QuickBooks or Xero connected to both gateways via a dedicated sync tool like Synder. This is a stopgap for teams under $500K ARR who haven't justified a full accounting integration yet. Once you cross that threshold, replace this with a real ERP connector.
The stack (5)
How it runs
- 1
Set up your Airtable Revenue Ledger base
Create an Airtable base called 'Revenue Ledger' with a table named 'Transactions'. Required fields: `transaction_id` (single line text, primary), `gateway` (single select: Stripe / Razorpay), `amount` (currency), `currency` (single line text), `status` (single select: captured / refunded / disputed), `captured_at` (date+time), `reconciled` (checkbox), `mismatch_flag` (checkbox), `mismatch_reason` (long text). This is your canonical ledger — both gateway syncs write here.
- 2
Build the Stripe nightly pull node in n8n
In n8n, create a workflow triggered by Schedule (cron: `0 1 * * *` — 1 AM daily). Add an HTTP Request node pointed at `https://api.stripe.com/v1/charges` with query params `created[gte]` set to yesterday's Unix timestamp using n8n's `$now.minus({days:1}).startOf('day').toUnixInteger()` expression, and `limit=100`. Authenticate via Header Auth with your Stripe secret key as `Bearer`. Add a pagination loop using the `has_more` field and `starting_after` cursor. One thing that bites people: Stripe returns amounts in the smallest currency unit (cents) — divide by 100 before writing to Airtable.
- 3
Build the Razorpay nightly pull node in n8n
Add a second HTTP Request node pointed at `https://api.razorpay.com/v1/payments` with query params `from` (yesterday 00:00:00 IST as Unix epoch) and `count=100`. Authenticate with Basic Auth using your Razorpay Key ID and Key Secret. Razorpay also returns amounts in paise — divide by 100. Add an IF node after: if `status == 'captured'`, proceed to write; if `status == 'failed'`, skip. Razorpay's pagination uses a `skip` param, not a cursor — loop by incrementing skip by 100 until the returned array length is less than 100.
- 4
Upsert both datasets into Airtable
After each gateway's fetch loop, add an Airtable node in 'Upsert' mode matched on `transaction_id`. Map fields directly: `transaction_id` → charge ID from Stripe or payment ID from Razorpay, `gateway` → hardcoded 'Stripe' or 'Razorpay', `amount` → converted float, `status` → gateway status field, `captured_at` → ISO timestamp. Set `reconciled` to false on every upsert — you'll flip it during the comparison step. Upsert (not insert) means re-running the workflow on failure won't create duplicates.
- 5
Run the comparison query against your ledger
Add an Airtable node to list all records from the last 24 hours where `reconciled = false`. Feed this into a Function node that groups records by `transaction_id` and checks: (a) does the same `transaction_id` appear with matching `amount` and `status` in both gateways where it should? (b) does any single-gateway record have an `amount` deviation above your threshold — e.g., ±$0.50 for FX rounding? Output two arrays: `clean_records` and `mismatch_records`. For `clean_records`, batch-update `reconciled = true` via Airtable's batch update endpoint.
- 6
Write mismatch reasons back to Airtable
For each record in `mismatch_records`, add an Airtable Update node that sets `mismatch_flag = true` and writes a human-readable string to `mismatch_reason` — e.g., 'Amount mismatch: Stripe $102.00 vs ledger $100.00' or 'Razorpay capture missing from ledger'. Create a filtered Airtable view called 'Mismatches – Review' filtered on `mismatch_flag = true` AND `reconciled = false`. That view URL is what you paste into the Slack alert. Whoever investigates should be able to open it and immediately understand what they're looking at.
- 7
Send the Slack alert with mismatch count and direct link
Add a Slack node targeting `#finance-ops` that fires only if `mismatch_records.length > 0`. Message template: `🚨 *Revenue Reconciliation – {{$today}}* • Stripe transactions processed: {{stripeCount}} • Razorpay transactions processed: {{razorpayCount}} • Mismatches found: {{mismatchCount}} 👉 <AIRTABLE_VIEW_LINK|Review mismatches in Airtable>`. If `mismatch_records.length === 0`, send a quieter confirmation to `#finance-ops-log` so there's an audit trail. Never send zero-mismatch alerts to a busy channel — alert fatigue kills this workflow's usefulness within a week.
- 8
Add a threshold escalation for large discrepancies
Before the Slack node, add an IF node: if any single mismatch has an `amount` delta greater than $500 or ₹40,000, route to a second Slack message posted to `#exec-alerts` with `@here` and the specific transaction ID and amounts. This separates routine rounding noise from financial errors that need same-day investigation. Set the threshold based on your average transaction size — for low-ACV SaaS ($20–50/month), a $100 delta is meaningful; for high-ACV ($500+/month), use $1,000.
Want me to build this for you instead?
Product Audit and CTO Mode run out of this same thinking. If you’re reading this thinking “I want this, but in my product” — let’s talk.
See servicesMore like this
Gmail Thread Aging + Stripe Invoice Overdue: Unified AR Follow-Up Digest to Slack via Zapier
Surface overdue Stripe invoices and the exact age of your last Gmail thread with that customer — every morning at 8:30, automatically — so AR follow-up stops living in someone's head.
Intercom Ticket Volume + Razorpay Failed Payments: Daily Support-Cost-per-Revenue Alert to Slack via Zapier
Catch the support cost blowout from Razorpay failed payments before your agents are already buried.
Outlook Calendar Load + HubSpot Deal Velocity: Weekly Ops Digest to Microsoft Teams
Every Monday at 07:30, your revenue team gets one Teams card: last week's deal pipeline movement next to each rep's actual meeting load — so you stop guessing whether low close rates are a pipeline problem or a capacity problem.