Dev.to · 4 min read

How GSTIN Checksum Validation Works (and Why It Isn't Enough)

How GSTIN Checksum Validation Works (and Why It Isn't Enough)

Every Indian GST invoice carries a 15-character GSTIN. If you've ever had to validate one in code, you've probably written a regex like this and called it done: /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/ That regex checks the shape of a GSTIN. It says nothing about whether the number is real. A typo that happens to land on a syntactically valid string will sail straight through it — and if that typo ends up on an invoice, it's the kind of thing that gets flagged months later during a GST audit. There's a cheap next step before you go anywhere near a network call: the GSTIN has a built-in checksum character, and you can verify it in a few lines with no API and no dependency. How the checksum works A GSTIN is 15 characters: 33AAACC1206D1ZN. The first 14 encode the state code, the business's PAN, and an entity number. The 15th is a checksum digit computed from the other 14, using a 36-character alphabet (0-9 then A-Z) and alternating weights of 1 and 2 — the same family of algorithm as ISO 7064 MOD 37-36. const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; function gstinChecksumIsValid(gstin) { if (!/^[0-9A-Z]{15}$/.test(gstin)) return false; let sum = 0; let factor = 1; for (let i = 0; i < 14; i++) { const codePoint = ALPHABET.indexOf(gstin[i]); const digit = factor * codePoint; sum += Math.floor(digit / 36) + (digit % 36); factor = factor === 1 ? 2 : 1; } const checksumIndex = (36 - (sum % 36)) % 36; return ALPHABET[checksumIndex] === gstin[14]; } gstinChecksumIsValid('33AAACC1206D1ZN'); // true gstinChecksumIsValid('33AAACC1206D1ZM'); // false — last char changed I hand-verified this against a real, currently-registered GSTIN (Central Warehousing Corporation, 33AAACC1206D1ZN) before trusting it enough to write this post — the algorithm above reproduces the N checksum character exactly. Python, same logic: ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' def gstin_checksum_is_valid(gstin: str) -> bool: import re if not re.match(r'^[0-9A-Z]{15}$', gstin): return False total = 0 factor = 1 for ch in gstin[:14]: code_point = ALPHABET.index(ch) digit = factor * code_point total += digit // 36 + digit % 36 factor = 2 if factor == 1 else 1 checksum_index = (36 - total % 36) % 36 return ALPHABET[checksum_index] == gstin[14] Drop this in ahead of any network call. It rejects a huge class of typos and malformed input for free, in constant time, offline. What a checksum can't tell you A checksum only proves the string is internally consistent — that it could plausibly be a GSTIN. It doesn't tell you: whether that GSTIN is actually registered whether the registration is still active, or was cancelled what legal name it's registered under, so you can catch a mismatch against the invoice You can construct a string that's checksum-valid and still refers to nobody, or to a business whose registration was cancelled last year. For that you need to ask the actual GST network, which means an API call. That's the part I build: gstinapi.in is a REST API that takes a GSTIN and returns the legal name, registration status, taxpayer type and registered address, live from India's official GSP network — no scraping, no cached copy. const { GstinApi } = require('gstinapi'); // npm install gstinapi const client = new GstinApi({ apiKey: process.env.GSTIN_API_KEY }); const result = await client.lookup('33AAACC1206D1ZN'); console.log(result.data.legal_name); // CENTRAL WAREHOUSING CORPORATION console.log(result.data.status); // Active from gstinapi import GstinApi # pip install gstinapi client = GstinApi(api_key=os.environ["GSTIN_API_KEY"]) result = client.lookup("33AAACC1206D1ZN") print(result["data"]["legal_name"]) # CENTRAL WAREHOUSING CORPORATION print(result["data"]["status"]) # Active Every account gets 100 free lookups a month, no credit card, and it renews every month rather than being a one-time trial credit — plenty to validate the idea before you need volume. Source for the client libraries: gstinapi-node and gstinapi-python. Full API reference is at gstinapi.in/docs. If you build GSTIN validation into something, I'd like to hear where the checksum check alone was enough and where you actually needed the live lookup — drop it in the comments.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Programming & Dev News