QR Parsing Guide
A parser is not a regex. It is a cursor through a length-prefixed stream. This page is for people writing one; the decoder how-to is for people using ours.
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).
