A GST Identification Number looks like a random 15-character code, but every position in it means something. Once you can read one, you can catch most typing mistakes before you ever call an API — and you will understand why a GSTIN that “looks right” can still be wrong.

The 15 characters, position by position

  • 1–2: State code. The two-digit code of the state or union territory where the business is registered — for example 07 for Delhi, 06 for Haryana, 27 for Maharashtra and 29 for Karnataka.
  • 3–12: PAN. The ten-character PAN of the business or proprietor. This is why one PAN can be linked to several GSTINs across different states.
  • 13: Entity number. How many registrations that PAN holds in the same state. It starts at 1, runs to 9, then continues with letters.
  • 14: Default character. For regular registrations this is the letter Z. Some special registrations use a different character here.
  • 15: Check character. Calculated from the first 14 characters. It exists to catch typing mistakes.

Take the commonly used example 27AAPFU0939F1ZV: 27 is Maharashtra, AAPFU0939F is the PAN, 1 means it is the first registration for that PAN in the state, Z is the default character and V is the check character.

Checking the format

The first test is the shape of the string. Two digits, five letters, four digits, a letter, then an entity character, one more character and the check character:

/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z][A-Z0-9][0-9A-Z]$/

Convert input to upper case and strip spaces before testing. People paste GSTINs from invoices, PDFs and WhatsApp messages, and stray spaces are the most common reason a correct number fails.

Checking the check character

The 15th character is calculated with a mod-36 checksum over the first 14 characters. Each character is mapped to a value (0–9 stay as they are, A is 10, B is 11, up to Z at 35). Every second character is multiplied by 2, the result is folded back into base 36, and the check character is whatever brings the total to a multiple of 36.

In PHP:

function gstin_is_valid( $gstin ) {
	$gstin = strtoupper( preg_replace( '/s+/', '', $gstin ) );
	if ( ! preg_match( '/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z][A-Z0-9][0-9A-Z]$/', $gstin ) ) {
		return false;
	}
	$chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
	$sum   = 0;
	for ( $i = 0; $i < 14; $i++ ) {
		$product = strpos( $chars, $gstin[ $i ] ) * ( $i % 2 ? 2 : 1 );
		$sum    += intdiv( $product, 36 ) + $product % 36;
	}
	return $chars[ ( 36 - $sum % 36 ) % 36 ] === $gstin[14];
}

This runs instantly, needs no network call and rejects most mistyped numbers — a swapped pair of characters or one wrong letter will almost always fail it.

A valid GSTIN is not a verified business

This is the part people miss. The checks above only prove the number is well-formed. They cannot tell you whether:

  • the GSTIN was ever issued,
  • the registration is still active, or has been cancelled or suspended,
  • the legal name and address match the business you are dealing with.

For that you need the GST system itself. You can look a number up manually with the “Search Taxpayer” option on the official GST portal, or use a verification service that queries the registration data for you. I built GSTIN.cloud and GSTVerify for exactly this step.

Putting it together

A sensible order for any form or system that accepts GSTINs:

  1. Clean the input: trim, remove spaces, convert to upper case.
  2. Check the format and the check character locally, and show the user a clear message straight away if either fails.
  3. Only then look the number up to confirm the registration, its status and the legal name.
  4. Compare the state code with the address the user gave you. A mismatch is worth a second look.

Doing the cheap local checks first keeps your verification calls for numbers that can actually exist, and gives people instant feedback while they are still typing.