A 4-partner CA firm in Indore handles GSTR-3B filing for 240 clients every month. Until October 2025, two associates spent 11 hours each on the 17th-18th of every month sending individual WhatsApp reminders, chasing acknowledgments, marking spreadsheets. We replaced that with a 19-node n8n flow that fires at 09:00 IST on the 18th, sends a personalised WhatsApp utility template to all 240 clients, and routes replies (READY / EXTEND / SKIP) into the firm's Notion board automatically. The associates now spend 90 minutes total handling exceptions instead of 22 hours sending messages. This is the build.
TL;DR — what this GSTR-3B reminder flow does
On the 18th of every month at 09:00 IST, n8n reads the firm's "GSTR_clients" Notion database (240 active clients with GSTIN, name, partner-incharge, default reminder template). For each client, it sends a personalised WhatsApp utility template with three quick-reply buttons: READY (filing data sent), EXTEND (need extra time), SKIP (NIL return). A separate webhook flow listens for replies, updates the client's row in Notion with status + timestamp, and DMs the partner-incharge if a reply is overdue by 24 hours.
Why this matters now — December 2025
Two reasons. The GSTR-3B due date moved permanently to the 20th of each month under the GST 2.0 rollout, and the non-editable Tables 3.1/3.2 from April 2025 mean filings now require validated supplier data days in advance — not the night before. CA firms can no longer leave reminders to the 19th. They need disciplined cadence on the 18th. Combined with the WhatsApp Business Cloud API quick-reply button feature (rolled out for India in mid-2025), we now have the components to make this fully self-serve.
A r/IndiaCAs thread from October 2025 on automation tools for filing reminders has 80+ practitioners debating exactly this — the consensus view is that WhatsApp templates with quick-reply buttons beat email by 4-5x on response rate.
The 4-block workflow
The actual n8n node JSON (3 critical nodes)
Node 1 — Schedule Trigger (monthly on 18th)
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 18 * *"
}
]
},
"timezone": "Asia/Kolkata"
},
"name": "18th 9am IST",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [240, 300]
} The cron expression 0 9 18 reads as: minute 0, hour 9, day-of-month 18, every month, every day-of-week. The timezone field is mandatory — without it, the cron runs at 14:30 IST instead of 09:00.
Node 2 — WhatsApp Cloud API send template with buttons
{
"parameters": {
"method": "POST",
"url": "https://graph.facebook.com/v21.0/{{ $env.WA_PHONE_ID }}/messages",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Authorization", "value": "Bearer {{ $env.WA_TOKEN }}" },
{ "name": "Content-Type", "value": "application/json" }
]
},
"sendBody": true,
"bodyContentType": "json",
"jsonBody": "={\n \"messaging_product\": \"whatsapp\",\n \"to\": \"{{ $json.phone }}\",\n \"type\": \"template\",\n \"template\": {\n \"name\": \"gstr3b_reminder_v2\",\n \"language\": { \"code\": \"en\" },\n \"components\": [\n {\n \"type\": \"body\",\n \"parameters\": [\n { \"type\": \"text\", \"text\": \"{{ $json.client_name }}\" },\n { \"type\": \"text\", \"text\": \"{{ $json.gstin }}\" },\n { \"type\": \"text\", \"text\": \"{{ $json.due_date }}\" }\n ]\n },\n {\n \"type\": \"button\",\n \"sub_type\": \"quick_reply\",\n \"index\": \"0\",\n \"parameters\": [\n { \"type\": \"payload\", \"payload\": \"READY_{{ $json.gstin }}\" }\n ]\n },\n {\n \"type\": \"button\",\n \"sub_type\": \"quick_reply\",\n \"index\": \"1\",\n \"parameters\": [\n { \"type\": \"payload\", \"payload\": \"EXTEND_{{ $json.gstin }}\" }\n ]\n },\n {\n \"type\": \"button\",\n \"sub_type\": \"quick_reply\",\n \"index\": \"2\",\n \"parameters\": [\n { \"type\": \"payload\", \"payload\": \"SKIP_{{ $json.gstin }}\" }\n ]\n }\n ]\n }\n}",
"options": { "timeout": 15000 }
},
"name": "Send GSTR-3B reminder",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [680, 300]
} Note the GSTIN baked into each button payload. When the client taps a button, Meta's webhook returns the payload (e.g., READY_27ABCDE1234F1Z5). Our reply-handler workflow parses that payload and finds the client without ambiguity — even if two clients share a phone number (it happens with family-run businesses).
Node 3 — Reply webhook handler (separate workflow)
{
"parameters": {
"httpMethod": "POST",
"path": "wa-reply",
"responseMode": "lastNode",
"options": {}
},
"name": "WA reply webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [240, 300],
"webhookId": "REDACTED"
}Followed by a Code node that parses the Meta webhook payload:
const body = $input.item.json.body;
const messages = body?.entry?.[0]?.changes?.[0]?.value?.messages || [];
if (messages.length === 0) return []; // status update, not a message
const msg = messages[0];
if (msg.type !== 'button') return [{ json: { skip: true, reason: 'not_a_button' } }];
const payload = msg.button.payload;
const [action, gstin] = payload.split('_');
if (!['READY', 'EXTEND', 'SKIP'].includes(action)) return [];
return [{
json: {
action,
gstin,
phone: msg.from,
timestamp: new Date(parseInt(msg.timestamp) * 1000).toISOString(),
message_id: msg.id
}
}];The WhatsApp template (the one to submit to Meta)
Submit this in Meta Business Manager under Templates > Create Template > Category: Utility:
Name: gstr3b_reminder_v2
Category: Utility
Language: English
Body:
Hi {{1}},
GSTR-3B for {{2}} is due {{3}}.
Please confirm:
- READY: filing data ready, share via portal
- EXTEND: need extra time (we will reach out)
- SKIP: NIL return for this period
— [CA firm name] auto-reminder
Buttons:
[Quick reply] READY
[Quick reply] EXTEND
[Quick reply] SKIPThree variables: {{1}} = client name, {{2}} = GSTIN, {{3}} = due date. Three quick-reply buttons. Meta approves utility templates with quick-reply buttons in 4-12 hours typically. Template approval is per business account, so once approved, you can use it across all clients of that account.
The credentials checklist
The pre-flight checklist (we run this on the 17th of every month)
- WhatsApp template still approved (Meta sometimes silently expires templates; check status weekly)
- Notion GSTR_clients database has zero stale rows (clients off-boarded marked inactive)
- All client phone numbers in E.164 format (+91 prefix, no spaces, no dashes)
- Reply webhook subscription confirmed working with a test button tap from your own phone
- Permanent system-user access token verified (not the 24-hour debug token that expires)
- Partner-incharge column populated for every client (chaser DMs need it)
- Notion replies database has the right schema (action, gstin, phone, replied_at)
- Error workflow set — failed sends should not block the rest of the loop
- Manual test send to a known phone scheduled for the 17th evening
- Backup notification path (email) for the 19th chaser if WhatsApp send fails
Common mistakes — symptoms first, fixes second
Symptom: "Template send returns 132001 invalid template." Cause: template name in API call does not match the approved template name (case-sensitive, includes version suffix). Fix: copy the template name from Meta Business Manager exactly. We use gstr3b_reminder_v2 after the v1 had a typo we fixed.
Symptom: "Client gets the message but the buttons are missing on iOS." Cause: iOS WhatsApp version below 23.10 has known button-rendering bugs. Fix: nothing you can do server-side. Most users are on current versions; for the few stragglers, the message body still works (READY / EXTEND / SKIP can be typed as plain text replies and parsed by a regex fallback).
Symptom: "Reply webhook fires but client name lookup returns nothing." Cause: phone number in Meta webhook is in different format from what we stored (e.g., 919876543210 vs +919876543210). Fix: normalise both ends to E.164 with explicit + sign. The reply handler should strip the + and prepend it consistently.
Symptom: "Same client received the reminder twice in 5 minutes." Cause: Meta retried the API call after a network blip and we did not idempotency-protect. Fix: add a message_id deduplication table — for each unique (client_phone, period) pair, only allow one send per day.
Symptom: "Partners complain the chaser DMs are too noisy." Cause: chaser fires 24 hours after the original send for any non-reply, including clients who replied via email outside the WhatsApp flow. Fix: add an "alternative_response" column to Notion that the partner can tick — chaser skips clients with that ticked.
Symptom: "WhatsApp limit of 1,000 unique users in 24 hours hit on month-end." Cause: tier-1 WhatsApp Business accounts have a 1,000-recipient daily cap. Fix: spread sends over 2 hours via the SplitInBatches node with a 5-second delay, or apply for tier-2 (10,000/day) once you have 1+ months of clean send history.
The reply handler in detail
When a client taps READY, the webhook handler updates Notion in 3 steps:
READY_27ABCDE1234F1Z5 → action=READY, gstin=27ABCDE1234F1Z5).A separate Slack DM goes to the partner-incharge: "Pinky Textiles confirmed READY for Nov 2025 GSTR-3B at 09:14 IST." Partners report this single change has improved their internal tracking more than anything else they have tried.
For more on how we wire similar Notion + Slack patterns, see our client-onboarding workflow. The 22-node onboarding flow uses the same Notion + WhatsApp pattern with different triggers.
Mini case study — 4-partner CA firm in Indore, 3 months live
The first install ran from October 18, 2025. Three monthly runs since. October numbers: 240 clients sent, 211 replies via button (88%), 17 EXTEND requests handled, 12 SKIP for NIL returns. November numbers: 240 sent, 217 replies (90%). December (today, 18th): 240 sent at 09:00, 152 replies in the first 4 hours. The two associates who used to spend the day on this now handle filing prep — one of them mentioned "I had time to read the GST council notification properly for the first time in two years."
For another high-volume Indian SMB workflow we built, see the BFCM cart-recovery flows — same WhatsApp Cloud API pattern with different triggers.
When not to build this
Skip this if (a) your client list is under 50 — the build cost is hard to amortise against ~5 hours of associate time saved per month, (b) your clients are not on WhatsApp — for older or extremely traditional client bases, this fails entirely; SMS is a worse fallback because two-way SMS in India is expensive and slow, or (c) you have ongoing trust issues with clients who suspect "auto-messages" — sometimes a text from the partner's personal number is what wins. We turned down a Mumbai chartered firm in 2025 for reason (c) — their senior partners did not want the firm to look automated.
For more on the AI automation services we offer to professional service firms, see the Softechinfra services overview for the full lineup.
FAQ
Do clients need to opt in to receive WhatsApp from the firm?
Yes, every recipient needs documented consent. We add a clause to the firm's engagement letter: "I consent to receiving filing reminders, document requests, and other transactional updates via WhatsApp at the registered mobile." The signed PDF lives in the client's Notion row.
What happens if a client replies with free text instead of a button?
The reply still arrives at the webhook. A fallback Code node looks for keywords (READY, EXTEND, SKIP, ready, "kar diya", "ho gaya") and tries to match. If no match, the message is forwarded to the partner-incharge via Slack with the raw text — they handle it manually.
How does the firm bill clients for SKIP/NIL returns?
Separately. The SKIP button just changes the workflow downstream — no filing data requested, just a one-line Notion entry. The firm's own billing system (we have not touched that) handles the NIL filing fee.
What if a client wants to switch off these reminders?
A "active" boolean column in the Notion GSTR_clients database. Set to false → client skipped on the next month's send. We also support a STOP keyword in the reply handler that auto-unsubscribes for 90 days.
Does this work for GSTR-1 too?
Yes, by changing the cron to day 11 (GSTR-1 due date is the 11th for monthly filers). The same template structure works — just rename to gstr1_reminder_v1. We have shipped the GSTR-1 variant for two firms.
How does the firm handle TDS / TCS reminders?
The same pattern, different cron (7th of each month for TDS). We are building a unified "compliance_reminder" workflow that reads a master Notion calendar and fires the right template per due date. Should ship in Q1 2026.
What about client GSTIN changes (mergers, demergers)?
The Notion row is the source of truth. The CA firm's account manager updates the GSTIN there; the next month's send picks up the new value. We log GSTIN history in a separate "client_history" Notion database for audit.
Want this GSTR reminder system built for your CA practice?
We ship the full 19-node flow — n8n on your infra, Meta Business + Notion setup, WhatsApp template approval, partner training — in 7 working days. Suitable for any CA or compliance firm with 50+ active clients on monthly GST cycles.
Book a 20-min CallFor more on Hrishikesh's perspective on automating professional services workflows, see the team page.
