Payload Decoding
Decoding is mechanical: split TLV, recurse into templates, verify Tag 63. The how-to-decode article adds operational refuse rules. This page is the parser sequence.
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.
