GST Reconciliation Failures: A Dev-Side Cheatsheet
For: A backend engineer or CTO at an Indian B2B SaaS or accounting platform whose automated GST reconciliation passes unit tests but produces growing ITC mismatch complaints from SMB users in production
Most GST reconciliation bugs in production are not matching-logic bugs. They are timing and state-transition bugs. Your engine treats GSTR-2B as a static monthly snapshot, but supplier amendments, QRMP quarterly cadences, and mid-period GSTIN cancellations make it a moving target. Re-fetch on a cadence, diff against the last known state, and log every transition — that fixes 70%+ of the complaints your support team files as "matching bug".
Below is the cheatsheet.
The five failure classes, ranked by ticket volume
| # | Failure class | Root cause | Where it surfaces |
|---|---|---|---|
| 1 | Stale GSTR-2B snapshot | Pulled once on the 14th; supplier amended on the 20th | ITC reversed in the next month's ledger |
| 2 | QRMP filer timing | Quarterly supplier's IFF vs GSTR-1 timing misread as "missing" | False mismatch flags mid-quarter |
| 3 | RCM entries treated as forward-charge | Self-invoice logic missing; supplier GSTIN not required | ITC double-counted or dropped |
| 4 | GSTIN status change mid-period | Supplier cancelled/suspended after invoice date | ITC claimed against ineligible supplier |
| 5 | Amended invoice not re-diffed | Original matched; amendment ignored | Value mismatch weeks later |
GSTR-2B: how often to actually refresh
The portal generates 2B on the 14th of the following month. That is not the end of the story. Suppliers keep filing GSTR-1 amendments, and 2B keeps mutating in subsequent generations.
- Minimum: re-fetch 2B on the 14th, 20th (before GSTR-3B due date), and the last day of the return period.
- Recommended: daily poll from the 14th to the 20th; weekly thereafter for the next two return cycles.
- Store every version. Hash the payload. Diff against last version. Persist the diff, not just the current state.
- Never overwrite. Append. Your audit trail is the only defence when a customer's CA asks why ITC moved.
State machine for a single invoice line
Model every invoice as a state machine, not a row. States a reconciliation engine must handle:
BOOKED_NOT_IN_2B— purchase register onlyIN_2B_NOT_BOOKED— supplier filed, buyer hasn't recordedMATCHED— value, GSTIN, invoice no., date all alignMATCHED_WITH_TOLERANCE— rupee rounding, ≤ ₹1 gapVALUE_MISMATCH— same invoice, different taxable value or taxAMENDED_UPSTREAM— 2B reflects a later amendmentSUPPLIER_GSTIN_INACTIVE— GSTIN cancelled/suspended after invoice dateRCM_SELF_INVOICE— no supplier match requiredINELIGIBLE_ITC— blocked credit under Sec 17(5)
Every nightly job transitions rows between these states. Log the transition with a reason code. If your logs only store the terminal state, you cannot debug the ledger complaint.
RCM: the four rules developers get wrong
- RCM invoices do not appear in supplier's GSTR-1, so they will never match 2B. Do not flag them as mismatches.
- The buyer generates a self-invoice under Sec 31(3)(f) for unregistered supplier RCM. Your data model needs a
self_invoiceflag distinct fromsupplier_invoice. - ITC on RCM is claimable only after the tax is paid in cash via GSTR-3B — not on booking. If your engine credits ITC at booking time, it will drift from the portal.
- Import of services and specified goods (GTA, legal, director fees) are RCM by default. Maintain a HSN/SAC → RCM-applicable lookup, versioned by notification date.
QRMP: why quarterly filers break monthly logic
QRMP suppliers file GSTR-1 quarterly but can push invoices to IFF in months 1 and 2 of the quarter. If they don't use IFF, the invoices appear in 2B only in month 3.
| Scenario | Invoice date | Appears in 2B of | Common bug |
|---|---|---|---|
| QRMP + IFF used | April | April 2B | None |
| QRMP + IFF skipped | April | June 2B | Flagged missing in April and May |
| QRMP + IFF used in May for April invoice | April | May 2B | Marked as date mismatch |
Fix: before flagging "missing in 2B", check the supplier's filing frequency via the GSTIN search API. If QRMP, suppress the flag until the quarter-end 2B is generated.
GSTIN validation edge cases
- Checksum: the 15th character is a Mod-36 checksum. Validate offline before any API call.
- Status is temporal. A GSTIN active today may have been cancelled retroactively. Store
status_as_ofwith every check. - Cancelled with retrospective effect: ITC claimed on invoices dated after the effective cancellation date must be reversed. This is where auditors catch platforms lying about "real-time validation".
- Suspended ≠ cancelled. Suspended GSTINs can be revived; do not purge history.
- Composition dealers issue bills of supply, not tax invoices. No ITC. Your OCR/import layer must reject these before they reach reconciliation.
- SEZ suppliers: invoices are zero-rated but appear in 2B differently. Handle the
supply_typefield explicitly.
Amended returns: the invisible mismatch
Suppliers can amend GSTR-1 invoices for up to the earlier of November of next FY or the annual return filing date. Each amendment generates a new 2B entry with amendment = true and a reference to the original.
- Match on
(supplier_gstin, original_invoice_no, original_invoice_date), not the current fields. - When an amendment lands, replay the original match, then apply the delta. Do not just overwrite.
- If the original was already claimed in 3B, the delta must flow to the current period's ITC — with an audit note.
Debug checklist when a user complains
- Pull all 2B versions for the disputed period. Diff them. Show the customer which supplier changed what and when.
- Check supplier filing frequency (monthly vs QRMP) as of the invoice date.
- Check supplier GSTIN status as of invoice date, filing date, and today. Log all three.
- Confirm RCM classification against the HSN/SAC lookup version in force on the invoice date.
- Rerun the state machine from the earliest 2B version forward. If the terminal state differs from what's in the user's ledger, you have a write-side bug, not a matching bug.
Things this approach is bad at
- Storage cost. Persisting every 2B version and every state transition inflates your Postgres/S3 bill. Partition aggressively; archive after two return cycles.
- Latency during peak filing. The GSTN APIs throttle around the 18th–20th. Build backoff and pre-fetch earlier in the window.
- UX complexity. Showing users "this changed because your supplier amended on the 22nd" is honest but overwhelming. Roll up in the UI, expose the detail on click.
- False stability signal. A quiet reconciliation run in month N does not mean month N is closed. Amendments in month N+3 can still move it.
If you're rebuilding an accounting or fintech backend that has to survive this kind of moving-target compliance, our writeup of the GimBooks accounting SaaS build covers the pipeline patterns in more depth, and the broader digital transformation practice page lists the modernization work adjacent to it.
Frequently Asked Questions
Why does my GST reconciliation match on the 15th but mismatch on the 25th?
Because GSTR-2B is not immutable after generation. Suppliers keep amending GSTR-1, and subsequent 2B pulls reflect those changes. Re-fetch 2B multiple times through the return window and diff versions instead of relying on the first snapshot.
How should we handle RCM invoices that never appear in GSTR-2B?
Do not attempt to match them against 2B at all. Flag them as RCM_SELF_INVOICE in your data model, credit ITC only after the RCM liability is discharged in cash through GSTR-3B, and maintain a versioned HSN/SAC-to-RCM lookup keyed by notification date.
What's the right way to validate a supplier's GSTIN status?
Validate the Mod-36 checksum offline first, then hit the GSTIN search API and store the returned status with a checked_at timestamp. Status is temporal — a GSTIN can be cancelled retrospectively — so always record status as of invoice date, filing date, and current date separately.
How do we detect QRMP suppliers before flagging false mismatches?
Query the supplier's filing frequency via the GSTIN details API. If QRMP and the invoice month is month 1 or 2 of the quarter, suppress the "missing in 2B" flag until the quarter-end 2B is generated, unless the supplier is using the Invoice Furnishing Facility.
How much history of 2B versions and reconciliation state should we keep?
At minimum, retain every 2B version and state transition for the current financial year plus one, since amendments are allowed until November of the next FY. Archive older data to cold storage rather than deleting — auditors and CAs routinely request historical diffs.
Can you give a fixed timeline to rebuild our GST reconciliation engine?
Not without seeing the current architecture, data volume, and integration surface. For a scoped assessment of your reconciliation stack and a rebuild plan, contact CodeNicely for a personalized review.
Found this useful? CodeNicely publishes engineering and product playbooks weekly. Browse the archive or tell us what you're building.
_1751731246795-BygAaJJK.png)