</> Payments ToolboxWebhook capture

Why your Bharat QR CRC is invalid

An EMVCo payload's checksum includes the literal characters 6304 that introduce it. Compute it over the payload without them and you get a plausible, wrong answer every time.

An EMVCo merchant QR — a Bharat QR, or the static UPI code taped to a counter — ends with tag 63, a four-digit checksum over everything before it. Get it wrong and no scanner reads the code at all: the checksum is validated before a single field is interpreted, so a payload with a bad CRC fails as one opaque unit.

Nearly every failure is one of two things.

1. The CRC covers the characters that introduce it

This is the one that catches almost everyone. The checksum is computed over the payload up to and including the literal characters 6304 — tag 63 and its length 04 — but not over the four hex digits of the value itself.

It reads like a specification error and it is not. Including the tag and length means a truncated payload cannot accidentally validate, and it is what every scanner implements.

What gets hashed — note the trailing 6304
…200162360107INV48210309Counter 10708TERM00426304

Here is the difference it makes on a real payload:

Correct — CRC over the payload including 6304
7CAC
Wrong — CRC stopping before 6304
FDE9

Both are four uppercase hex digits. Both look completely reasonable. Only one is accepted, and the failure mode is the same as if you had corrupted the payload.

2. “CRC-16 CCITT” names at least four different algorithms

EMVCo specifies CRC-16/CCITT-FALSE. Precisely:

  • polynomial 0x1021
  • initial value 0xFFFF
  • no reflection of input or output
  • no final XOR

The trouble is that several mutually incompatible CRC-16s answer to the name “CCITT”, and several of them also use 0x1021. CRC-16/KERMIT reflects its input and output; CRC-16/XMODEM starts from zero. Pick a library by name alone and you can end up with a checksum that is perfectly self-consistent — your encoder and your decoder agree — and rejected by every real scanner.

That value — 29B1 — is computed by this site's own implementation as this page renders, not quoted from a table, so it is the same code the decoder runs.

Two smaller things

Uppercase, four digits, zero-padded. A checksum of 0x0A1B is 0A1B, not a1b or A1B. Some readers tolerate lowercase; not all do, and the length is not negotiable because tag 63 declares its length as 04.

The CRC must be last. Tag 63 is the final field. Appending anything after it — a stray newline from a text editor, a trailing space from a copy and paste — invalidates the code even though the CRC value itself is right.

You cannot hand-edit a payload

Because the checksum covers everything, changing any character invalidates it: an amount, a merchant name, even a length byte. This surprises people who successfully edit a payload, see all the fields decode correctly in a viewer, and then find nothing scans. Recompute the CRC after any change.

The Bharat QR and EMVCo decoder validates the checksum and shows both what it computed and what the payload claims, so you can see which of the two mistakes above you have. It parses the TLV structure into named tags at the same time, which is usually enough to spot a wrong length as well.

If your string starts with upi:// rather than 0002, it is not an EMVCo payload and has no CRC at all — that is a UPI deep link, and it belongs in the UPI link and QR tool instead.

Check it against the tool

Frequently asked questions

Does the CRC include the 6304 prefix?
Yes, and this is the mistake that catches almost everyone. The checksum is computed over the entire payload up to and including the characters 6304 — tag 63 and its length 04 — but not over the four hex digits of the value itself. Stopping before 6304 produces a well-formed, consistently wrong checksum.
Which CRC-16 variant does EMVCo use?
CRC-16/CCITT-FALSE: polynomial 0x1021, initial value 0xFFFF, no reflection of input or output, and no final XOR. The name 'CCITT' is claimed by at least four mutually incompatible CRC-16s, several of which also use 0x1021, so picking a library by name alone is how people end up with a checksum that is internally consistent and rejected by every scanner. Check your implementation against the standard vector: the ASCII string 123456789 must produce 0x29B1.
I edited one character in the payload and now nothing scans. Why?
Because the CRC covers the whole payload, so changing any field invalidates it — including changing a length byte, or an amount. A QR payload is not editable by hand: recompute the checksum after any change, or the code is rejected before a single field is read.