How to Decode a Payment QR
Decoding is the inverse of generation: split the ASCII payload into TLV objects, check lengths, then verify CRC-16. Do this before you treat a scanned merchant QR as payment instructions. This decoder runs locally in the browser.
Start from the raw string, not the pixels
Cameras give you a bitstream; wallets and this site care about the decoded alphanumeric payload. If two scanners disagree, compare the strings, not the images. A smudged sticker can yield a truncated string that still looks like a QR.
Step-by-step with the on-site decoder
1) Open https://emvqrhub.com/decode/ and paste a raw payload (or scan an image).
2) Confirm Tag 63 validates before trusting any business field.
3) Expand Merchant Account Information (26–51) and record GUID + account proxies.
4) Check Tag 01 against presence of Tag 54 (11 without amount for stickers; 12 with amount for billed QR).
5) Export or photograph the tag table for support tickets — wallets reject for membership and UI reasons that CRC alone cannot explain.
Walk TLV left to right
Each object is a two-digit tag, a two-digit length, then exactly that many characters of value. Nested templates (26–51, 62, 64) repeat the same rule inside the value. If a length overruns the remainder of the string, stop — the payload is malformed even if a CRC was appended.
You should see Tag 00 first with value 01 for current MPM. Tag 01 should be 11 or 12. Tag 63 should be last. Anything after the CRC is unexpected.
CRC is an integrity check, not authentication
CRC-16/CCITT-FALSE over the payload through the 6304 prefix detects accidental edits and many generator bugs. It does not prove who created the QR. A fraudster can compute a valid CRC on a malicious MAI just as easily as a merchant can.
If CRC fails, do not try to “fix” the last four hex digits by hand unless you regenerated the whole string. Wrong CRC plus a plausible merchant name is a classic overlay-sticker attack.
Wrong versus right checks
Wrong: Tag 53 = INR. Right: Tag 53 = 356. Wrong: Tag 01 = 11 together with a locked Tag 54 that the cashier cannot change. Right: static 11 without amount, or dynamic 12 with Tag 54. Wrong: two MAI templates with conflicting GUIDs and no documented scheme. Right: the template IDs your acquirer listed.
Worked decode of a static sample
Take 00020101021126360014A000000677010111011300660000000005204581253033565802IN5910EMVQRHUB6007DELHI6304A13F. After splitting you get format 01, POI 11, MCC 5812, currency 356, country IN, name EMVQRHUB, city DELHI. Confirm CRC against the same string. If your decoder reports a different name, a length was misread.
When to refuse the payment
Refuse or escalate when CRC fails, Tag 00 is missing, country/currency pair is nonsense (for example country TH with currency 356 and no documented reason), merchant name is blank, or the GUID is a well-known test AID used as if it were production.
Use the Common EMV QR Errors guide on this academy for CRC failures, POI conflicts, and TLV length mismatches. Then re-generate from the EMV studio rather than patching bytes.
Official spec versus this page
EMVCo publishes the QR Code Specification for Payment Systems (EMV QRCPS). This page restates the operational checks developers hit in tools. It is not a substitute for the specification and EMV QR Hub is not affiliated with EMVCo.
Photograph plus string
When a shop asks for help, take a photo and also copy the decoded text. Photos compress; strings do not. Debug from the string.
Next steps after a valid decode
If CRC and tags look right but nobody can pay, you have an interoperability problem. Jump to that article rather than recomputing CRC.
Pixels lie; the ASCII payload does not
Two cameras can disagree on a smudged sticker. Compare strings, not images. When a shop asks for help, photograph the code and copy the decoded text. Photos compress; strings do not. Debug from the string in /decode/. If the string is truncated, the symbol is a camera/print problem, not a CRC-polynomial problem.
Walk TLV left to right: two-digit tag, two-digit length, value, nested templates inside 26–51 and 62. Tag 00 first (01), Tag 01 is 11 or 12, Tag 63 last. Overrun or leftover characters: malformed. CRC is integrity, not authentication — a valid checksum on an overlay still pays the overlay’s MAI.
Worked static decode you can repeat
Paste a studio-generated static payload. Confirm CRC valid, POI 11, Tag 53 numeric, no Tag 54, Tag 59/60 readable, MAI tree present. Then flip the last CRC nibble and confirm invalid. Then change Tag 53 to INR in a hex editor (and fix length if needed) to see a semantic fail that still checksums if you are careless. That trio is the core lab.
When CRC and tags look right but nobody can pay, you have interoperability (GUID/membership) or a physical scan issue. Jump to those articles rather than recomputing CRC. Official spec versus this page: emvco.com wins; we show original decoder output.
Refuse versus warn
Payment-time decode should refuse malformed TLV, CRC invalid, Tag 63 not last, missing MAI, missing mandatory identity tags, and alphabetic Tag 53. Laboratory decode may show a partial tree. Do not ship laboratory leniency in a wallet. This site’s decoder is a lab tool with clear verdicts so engineers can see why a string is bad.
Next steps after a valid decode: if you are generating, print at deploy size and scan from queue distance. If you are investigating a live shop, check the standee against the stored payload hash. Operator: Pankaj Kalra, not affiliated with EMVCo.
Support ticket template
Ask for: raw payload, app name, expected merchant, amount on receipt, photo of standee. Decode locally; do not ask customers to email production strings to a public inbox. Redact MAI in screenshots if policy requires.
Accessibility and glare
If older customers struggle to scan, check print contrast and size before blaming TLV. Decode the string from a staff phone scan; if that string is truncated, fix the symbol. If the string is full and CRC valid, move to wallet interoperability.
Algorithm
While bytes remain: read tag (2), length (2), value (length). If the tag is a template, recurse. Stop if length overruns. After the root list, CRC must match.
Output you should show a human
A table of tag, meaning, value. Highlight CRC, POI, currency, name. Do not only dump hex.
Use the live decoder
Paste a payload from the cookbook. If your own parser disagrees on name or city, your length math is wrong.
Nested decode of Tag 26
After you slice Tag 26’s value, run the same TLV loop: inner 00 should be a GUID, then scheme fields. If inner parse throws, the defect is inside the template, not in country or city tags that happen to sit later on the root.
Ambiguous digits
Because tags and lengths are digits, a desynced parser can invent plausible looking tags (for example reading 58 from the middle of an amount). Always re-sync from byte 0 after a failure rather than skipping one object.
CRC timing
Verify CRC after the tree parses cleanly. A CRC pass on a string you could not fully parse is a coincidence or a truncated compare. Our decoder reports both parse errors and CRC so you do not mix the two.
Local processing
Paste payloads into Decode on this site; they stay in the browser. Do not email live merchant QR strings to a shared inbox if they contain production account proxies.
A left-to-right algorithm you can implement
Start at index 0. Read two characters as the tag, two as the length N, then take the next N characters as the value. Advance the cursor by 4+N. Repeat until the remaining string is empty. If N is not an integer, if 4+N overruns the remainder, or if you finish with leftover characters that are not a complete object, the payload is malformed. Nested templates (26–51, 62, 64) run the same loop on the value bytes only.
Do not split on commas, spaces, or “6304”. Those heuristics work on one golden sample and fail on the next merchant name. EMVCo’s QR Code Specification for Payment Systems (see the QR technology page on emvco.com) is the normative description of this stream; this article is an implementation note for people using our decoder.
Worked root walk of a static sample
Take the illustrative static string used elsewhere on this site: it begins 000201010211… so the first object is Tag 00 length 02 value 01, then Tag 01 length 02 value 11. Next you should hit a Merchant Account Information tag in 26–51. After the identity block you expect 52 (MCC), 53 (numeric currency), 58, 59, 60, and finally 63 with four hex characters. If Tag 54 appears on this static sample, the generator mixed POI with amount — that is a product bug, not a decoder bug.
When Decode on this site shows a tag table, it is this walk rendered as rows. If your library’s rows disagree, dump both tables beside the raw string. The first mismatched tag is where the cursor desynced. Fix that length; do not “skip until CRC.”
Nested MAI without losing the outer length
Tag 26’s value is itself TLV: inner 00 is usually a GUID, then scheme-specific 01–99 fields. The outer length must equal the exact character count of that inner stream. The most common production defect is editing an account proxy inside 26 and forgetting to rewrite the outer two-digit length. Downstream tags then get swallowed into the account blob, so Tag 59 looks like a fragment of a PAN or UEN.
A practical check: after parse, the sum of 4+length for every root object must equal the payload length. If it does not, you either dropped CRC from the compare or you accepted an overrun. Our decoder reports nested templates as trees so you can see whether the GUID is where you think it is.
CRC after a complete tree, not before
Compute CRC only after the TLV walk succeeds and Tag 63 is last. A CRC match on a string you could not fully parse is not a pass — it is a truncated compare or a coincidence. Conversely, a CRC fail on a tree that otherwise looks legal means you should not attempt payment, even if name and city “look right” to a human.
Paste production-like strings into /decode/ rather than mailing them to a shared inbox. Decoding on this site stays in the browser. Live account proxies are operationally sensitive even when they are not card PANs.
What to show an operator versus a library log
Humans need merchant name, city, country, currency numeric code, POI (static vs billed), amount if present, CRC valid/invalid, and a collapsed MAI summary. Libraries need the full tag map plus cursor offsets for failures. Mixing the two views is how support tickets become un-reproducible: someone screenshots the pretty QR and omits the string.
If you are writing a parser, add a dump mode that prints tag, length, value, and nested children. Compare that dump to Decode on the same fixture. When they match, you can trust your unit tests; when they do not, EMVCo’s spec wins, not either UI.
When to stop parsing and ask for a new sticker
If the string is truncated, desynced, or CRC invalid after a physical scan, ask for a reprint or a screen QR from the POS — do not attempt payment on a partial string. If the string is perfect in Decode but fails in every wallet, stop parsing and call the acquirer.
Cursor, not split-on-comma
There are no delimiters between objects. Length is the only truth. Off-by-one length is why merchant names swallow cities.
Templates
26–51, 62, 64: parse inner TLV until the outer length is consumed. Leftover inner bytes or early exit are both defects.
CRC last
If 63 appears twice, or data follows 63, reject. Compute CRC on the prefix the spec defines; do not hash the hex digits of the checksum.
Test vectors
Use the payload cookbook static sample, then a mutated CRC, then a truncated string. Three outcomes: valid, CRC fail, malformed.
Integer lengths are characters, not bytes of UTF-16
MPM payloads are ASCII-oriented. If you count Java UTF-16 code units for a name that only uses ASCII, you may still be fine. If you allow non-ASCII in Tag 59, confirm the spec’s character set for your implementation before you invent a length.
Reject unknown mandatory omissions
A parser used at payment time should fail closed when 00, 01, 53, 58, 59, 60, or 63 is missing. A laboratory parser can be more lenient to show how far it got. Do not ship the laboratory mode in a wallet.
Fuzzing
Flip random length digits on a valid sample and assert you never execute a payment. You should see either malformed or CRC invalid. Infinite loops usually mean you did not bound remaining length.
Compare with this decoder
If your library and emvqrhub.com/decode disagree on the same string, dump both tag tables. The mismatch is the bug. Then read EMVCo QRCPS; we are not the spec.
Write a bounded state machine, not a regex
A parser that searches for 6304 or for 5910 will eventually match those digits inside an amount, a city, or a GUID. Walk from offset 0 with an explicit remaining-length check on every object. Cap the loop by payload length so a hostile length digit cannot spin forever. If you cannot finish the loop, fail closed for payment; laboratory UIs may show a partial tree, but wallets must not.
Integer lengths in MPM count characters in the ASCII-oriented payload, not UTF-16 code units and not UTF-8 bytes of a separately encoded string. If you allow non-ASCII in Tag 59, confirm the character set your target spec version allows before you invent a length. Then round-trip: parse, serialize, compare.
Mandatory omissions should fail payment, not just warn
A wallet-grade parser should refuse when Tag 00, 01, 53, 58, 59, 60, or 63 is missing, or when no MAI template in 26–51 is present. A laboratory parser (like a decoder used by engineers) can be looser so you can see how far the walk got. Do not ship laboratory mode in production pay. The difference is the whole point of having two tools.
Unknown optional tags are a different story. If you drop them on re-encode, you change CRC and you may drop a field a scheme required. Preserve unknown objects in order unless you are deliberately building a new payload from a form.
Fuzzing that proves you fail closed
Start from a valid sample from the cookbook. Flip random length digits, swap Tag 63 to the middle, replace Tag 53 with INR, and truncate the last two characters. Assert that your payment path never returns “ready to pay.” You should see malformed or CRC invalid. Infinite loops almost always mean you did not bound remaining length.
Compare failures with /decode/ on the same mutated string. If this site rejects and your library accepts, your library is the bug. If both accept a Tag 63-not-last payload, both are too lenient for MPM as commonly specified — tighten, then re-read EMVCo QRCPS rather than copying our UI.
Nested parse of additional data (Tag 62)
Tag 62 is a template. Inner tags commonly carry bill number, mobile, store, loyalty, and consumer data depending on the scheme. A parser that treats 62’s value as a opaque string cannot validate inner lengths. A parser that assumes a fixed inner layout will break when a market adds one sub-tag. Walk inner TLV the same way as the root, and do not require Tag 62 on static stickers.
When bill number is present, operations will want it in the acquirer report. That is a host concern. The parser’s job is to extract it without desyncing the root. If inner 62 overruns, fail the payload; do not skip to CRC and hope.
Using this site as a second implementation
Independent parsers catching the same defects is how you gain confidence without treating emvqrhub.com as a spec. Generate ten legal payloads in the EMV studio, decode them here, decode them in your library, and diff tag tables. Then run the negative fixtures from the testing article. When they match, freeze the fixtures in git.
We are not EMVCo. If a future QRCPS revision changes a rule, the PDF on emvco.com wins. Update your golden vectors from the document and from your scheme kit, not from an old Academy screenshot.
Parser code review checklist
Bounded loop? Two-digit tags and lengths? Template recursion? Fail closed on overrun? CRC after full parse? Separate lab vs pay mode? If any answer is no, do not ship to a wallet.
Ship parsers as libraries
One parser for web lab, one for mobile pay — share golden vectors. Divergence between them is how web Decode says valid and mobile says malformed. Single library, two modes (lab vs strict).
