EMV QR Hub
EMV QR Hub
Technical Guides

TLV Format in EMV QR Payloads

EMVCo MPM payloads are not JSON. They are a tight Tag-Length-Value stream. If you mis-count length, every later field is garbage even when CRC was computed on that garbage.

The three fields

Tag: two digits (00–99 at the root for MPM). Length: two digits, the count of characters in the value (00–99). Value: exactly that many characters, which may themselves be a TLV list for templates.

Worked Tag 54

5406125.00 means tag 54, length 06, value 125.00. If you write 5405125.00 the parser eats 125.0 and then treats the leftover 0 as the start of the next tag — a classic desync.

Nested templates

Tag 26’s value is another TLV stream (GUID, account ids). The outer length must equal the inner stream length. Students often set outer length to “about 36” from a sample and then change an inner id.

Why generators exist

Hand-concatenating TLV is how samples rot. Use the EMV studio, then inspect with Decode. Specification details: EMVCo QRCPS. We implement the encoding; we do not publish the spec.

Leading zeros in tags

Tag 00 is two characters, not one. Writing 0/2/01 is not MPM. Generators must emit zero-padded tags and lengths.

Maximum 99

A two-digit length cannot describe a 100-character value. Long additional data belongs in a template that is itself chunked, or it does not belong in the QR. Do not invent three-digit lengths.

Round-trip test

Parse a payload to a tree, serialize again, compare strings. If they differ only by CRC hex case, normalize. If tags move, your serializer is not stable enough for signatures or logs.

Human dump

Our decoder pretty-prints tag meaning next to values so you do not have to memorize 52 versus 53. Your library should offer the same dump in tests.

Worked nested Tag 26 (illustrative)

Suppose a root object reads 26360014A00000067701011101130066000000000 — that is Tag 26, length 36, then 36 characters of inner TLV. Inner 00 length 14 might be a GUID prefix such as A000000677010111, then inner 01 length 13 holds an account proxy. If you change the proxy to 15 characters and leave outer length 36, the next root tag is eaten. Decode will show a nonsense merchant name; the fix is the outer length, not Tag 59.

Students often copy length 36 from a blog sample forever. Length is not a magic constant. It is the character count of the current inner stream. Recalculate it every time an inner field changes, the same way you recompute CRC every time any earlier field changes.

Why two-digit tags and lengths exist

MPM chose a dense ASCII encoding so a phone camera can recover a short string. Tags 00–99 and lengths 00–99 are the budget. That is why merchant name is capped (commonly 25) and city (commonly 15): the encoding cannot grow a field to a paragraph. If you need a long invoice narrative, it does not belong in the QR; put a short bill number in Tag 62 and keep the narrative on the ticket.

Do not invent three-digit lengths or one-digit tags to “save space.” Wallets will not parse them. If you are out of budget, shorten Tag 59, drop unused 62 sub-tags, or use a higher QR version / error-correction trade-off at the symbol layer — a different problem from TLV.

Desync symptoms you will actually see

Classic desync: Tag 54 written 5405125.00 instead of 5406125.00. The parser consumes 125.0, then the leftover 0 starts a phantom tag. Downstream country and name become fragments. Another classic: UTF-8 byte counts used as length while the payload is ASCII digits plus a dotted amount. A third: stripping leading zeros from Tag 00 so the stream starts 0201… which is not MPM.

When Decode shows CRC invalid and a truncated tree, fix the first bad length. Recomputing CRC on a desynced string just checksums garbage. The round-trip test (parse → serialize → compare) catches serializer bugs that unit tests on a single golden string miss.

Templates versus primitive values

Primitive tags (53, 58, 59, 54, 63, …) have values that are not TLV. Template tags (26–51, 62, 64, some unreserved) have values that are TLV. If you pretty-print a template as a single string, you will miss inner defects. If you try to parse a primitive as TLV, you will invent tags from the digits of an amount or a city.

Our decoder labels templates as trees and primitives as scalars so you can see the difference. When you write tests, assert both. EMVCo’s QR spec is the authority for which IDs are templates; we mirror that in software for the fields the studio actually emits.

Generator discipline

Hand-concatenating TLV is how cookbook samples rot. Use the EMV studio, copy the payload, inspect in Decode, then store the exact string next to the print job. If you must assemble TLV in code, centralize tag/length padding in one function and fuzz it. Two-digit zero-padding is not optional.

This academy will keep showing small worked fragments rather than pasting EMVCo PDFs. For normative field lists, download the current QR specification set from emvco.com. We are independent of EMVCo, LLC.

Teaching TLV with a highlighter

Print a payload in monospace, draw boxes around tag/length/value triples, and colour nested MAI. One page of highlighting beats ten slides. Then enter the same string in Decode and confirm your boxes match the UI rows.

Capacity planning

Before adding verbose Tag 62, estimate total string length and check QR version capacity at your print size. TLV correctness plus ISO symbol capacity are joint constraints.

Explore Related Guides

EMV QR Generator
Create merchant-presented EMV QR payment payloads.
QR Parser & Decoder
Decode QR payloads and inspect EMV tags.
Legal Disclaimer:EMV QR Hub is a technical utility. We do not process financial transactions or store sensitive payment data. Not affiliated with EMVCo, LLC.