EMV Payment QR Decoder
Decode an EMVCo merchant-presented QR payload field by field, recompute its CRC-16/CCITT-FALSE checksum independently, and identify the national scheme behind PayNow, DuitNow, QRIS, PromptPay, VietQR and FPS codes.
EMV Payment QR Decoder
0002
Paste a payload to begin
The checksum is recomputed here from the payload's own content, not read back from it.
Findings
Mandatory fields, value shapes, and the two questions kept apart.Every field in the payload
| Tag | Field | Length | Value |
|---|
Which scheme this belongs to
Read from the identifier at subtag 00 of each merchant account template.What the checksum covered
Up to and including the literal6304.
Nothing you paste or scan leaves your browser. The parser, the tag tables and the checksum all run in this page; there is no request carrying your payload, no storage and no analytics event containing it. A QR image is read the same way — drawn to a canvas in this tab and decoded by a background script, never uploaded. That matters here more than on most tools, because a merchant-presented QR contains a payee's account proxy — a UEN, a mobile number, a bank account — and a decoder that phoned home would be collecting exactly the identifiers you came to inspect. You can check by decoding with your network disconnected. For images, read one image first: the first scan fetches the decoder script from this site — the script, not your picture — and after that it works offline too.
How to decode a payment QR payload
Photograph or screenshot the QR, then drop the image onto the tool, paste it, or choose it with Read a QR image. It is decoded here in your browser and never uploaded. If a QR reader app has already given you the text, which begins 0002, paste that instead.
The decode runs as you type, entirely in this page. Surrounding whitespace is trimmed; whitespace inside the payload is left alone, because it changes the checksum and you need to see that it does.
The left one is what the payload claims in tag 63. The right one is computed here from the content. If they differ, something changed after the code was generated.
A payload can verify perfectly and still be missing a mandatory field, because the CRC only proves nothing changed since it was written. The findings list covers what the checksum cannot.
Every data object is listed in payload order with its tag, name, declared length and value. Nested templates are indented under their parent, which is where the national scheme puts the payee identifier.
One format, five national payment schemes, and a checksum everyone reimplements
Scan a merchant's QR code in Singapore, Kuala Lumpur, Jakarta, Bangkok or Ho Chi Minh City and the thing your phone actually reads is the same in all five places: a single line of text, a few hundred characters long, holding every field as tag, length, value with no separators and no escaping. EMVCo calls it Merchant Presented Mode. PayNow, DuitNow, QRIS, PromptPay and VietQR are national schemes with different regulators, different settlement rails and different politics, and all of them carry their payload in that format — which is why one decoder can read all of them, and why a bug in a decoder is a bug in every market at once.
Reading the string
The structure is deliberately simple enough to parse with a loop and a substring. Two digits of tag, two digits of length, then exactly that many characters of value, repeated until the string runs out. The first data object is always tag 00, the payload format indicator, and the last is always tag 63, the checksum. In between sit the merchant's category code, the transaction currency as an ISO 4217 number, the amount if there is one, a two-letter country code, the merchant's name and city, and — the interesting part — one or more merchant account information templates in the range 26 to 51, each of which is itself a TLV string with a scheme identifier at its subtag 00.
That nesting is where the national schemes live, and it is not uniform. Tags 02 to 25 hold a plain value rather than a template. Tag 62 holds nested data too, and its own subtags 50 to 99 nest again. VietQR goes one level deeper than anyone else: its beneficiary organisation is a TLV string inside a TLV string inside a TLV string, with the bank's BIN and the account number as separate fields at the bottom. A decoder that tries to work out whether a value "looks like" TLV rather than applying a rule per scheme will eventually shred a merchant identifier that happens to begin with two digits, which is most of them.
The checksum, and the three ways to get it wrong
Tag 63 carries four hexadecimal digits: a CRC-16 over everything that came before. It exists to catch a code that has been damaged, truncated or retyped, and recomputing it is the only way to know that the string in front of you is the string the acquirer generated. Three details trip up implementations, and all three produce a decoder that rejects perfectly good QR codes.
First, the variant. It is CRC-16/CCITT-FALSE — polynomial 0x1021, initial value 0xFFFF, no reflection
of input or output, no final XOR. The reflected variant that is also commonly called "CRC-16/CCITT"
shares the polynomial and agrees on nothing else. Over the catalogue check string "123456789" the correct
one gives 29B1 and the reflected one gives 2189. Second, the range. The checksum covers the payload up to
and including tag 63's own tag and length — the literal characters 6304 — and stops only
before the four digits themselves. Stopping four characters earlier is the single most common defect in
hobby implementations. Third, the units. The length fields count characters, but the checksum is taken
over bytes, and for any payload written in Latin script those are the same number, so the mistake hides
until someone scans a code with a Chinese, Thai or Vietnamese merchant name on it.
A checksum that verifies tells you the payload has not changed since it was written. It tells you nothing whatsoever about whether what was written was true, or about who wrote it.
Why this matters in this region specifically
Southeast Asia adopted merchant-presented QR faster and more completely than almost anywhere else, largely because it let small merchants accept digital payment without a terminal. A hawker stall, a night-market stand or a motorbike repair shop needs a printed sticker and a bank account, not hardware. The consequence is that the security model rests on a sticker: the payee identifier is printed on a piece of paper stuck to a wall, and swapping that paper is the entire attack. That risk is well known, and it is worth being precise about what a decoder can and cannot do about it. It can tell you which scheme a code belongs to, which country registered the identifier, what account proxy it will pay, whether an amount is fixed or open, and whether the string is internally consistent. It cannot tell you that the sticker belongs to the shop you are standing in.
The cross-border linkages make reading the payload more useful, not less. Several ASEAN schemes now accept each other's codes, so a payload generated under one country's rules is routinely read by a wallet built for another's. When something goes wrong in that path — an amount that a wallet refuses, a merchant name that arrives truncated, a code that one app reads and another rejects — the answer is almost always visible in the fields. A merchant name over the 25-character limit gets cut. An amount written with a thousands separator is not a valid amount. A dynamic code with no amount in it will be read as one the payer must fill in. None of that is diagnosable by looking at the picture.
What this tool does not do
It does not use your camera directly — photograph the code and give it the picture, which it reads in this page without uploading it. It does not generate codes, and deliberately so: a tool that produced a payment QR carrying somebody else's merchant name would be a forgery kit. It does not contact the scheme, so it cannot tell you whether an account proxy is real, registered or active; nothing about the identifier's format proves anything about the payee. And its scheme registry is a list of identifiers that have been published — when an identifier is not in it, the tool says so and infers only what the string's own shape supports, which for a reverse-domain identifier is the country that registered it, and nothing more.
The checksum covers its own tag number
The checksum covers its own tag and length. The input to the CRC ends with the literal characters 6304 and stops before the four hexadecimal digits — a range that is off by exactly four characters is the most common defect in hand-rolled decoders.
The algorithm is CRC-16/CCITT-FALSE: polynomial 0x1021, initial value 0xFFFF, no reflection, no final XOR. Over the standard check string "123456789" it produces 29B1. The reflected variant, also routinely called "CRC-16/CCITT", produces 2189 and rejects every real payment QR.
Tags 02 to 25 hold a plain value; tags 26 to 51 hold a nested TLV template with a scheme identifier at subtag 00. Parsing the first group as if it were the second shreds any account number that begins with two digits.
Length fields count characters while the checksum counts bytes. EMVCo's own worked example proves both at once: its alternate-language merchant name is four Chinese characters carrying the length field 04, and its stated checksum only reproduces over the twelve UTF-8 bytes.
The point-of-initiation method is the difference between a printed sticker and a till. Value 11 means static — one code, reused, with the payer typing the amount. Value 12 means dynamic — generated per transaction, normally with the amount already fixed.
Schemes identify themselves two different ways and the standard permits both. PayNow writes the text SG.PAYNOW; PromptPay writes the registered application identifier A000000677010111. A decoder needs to match both forms.
A QRIS payload usually carries two merchant templates at once: an acquirer's own template and the national one at tag 51, whose subtag 02 holds the merchant's National Merchant ID.
VietQR nests a third level. Tag 38 contains the scheme identifier, then a beneficiary organisation that is itself a TLV string holding the bank BIN and the account number as separate fields.
HKICL publishes the identifier for Hong Kong's FPS QR in lower case as hk.com.hkicl, while most schemes use upper case. A decoder that compares identifiers case-sensitively silently fails to recognise it.
A valid checksum and a valid payload are different claims. A string with no merchant account information at all — no payee, no scheme — can carry a perfectly correct CRC, because the CRC only proves that nothing changed after it was computed.
Frequently asked questions
-
Either the picture or the text inside it. Every merchant-presented payment QR encodes a single line of text beginning 0002, which is tag 00 with a length of 2. Give the tool a photo or screenshot and it reads that line out of the image itself; if a QR reader app has already shown you the text, paste that instead. Either way the same decoder runs on the same string.
-
No. The parser, the tag tables and the checksum all run in your browser, and a QR image is decoded there too — drawn to a canvas in this tab and read by a background script. There is no network request carrying what you paste or scan, nothing is stored, and no analytics event contains it. The one request the image path makes is to fetch the decoder script from this site the first time you use it, so to confirm it offline, read one image first and then disconnect.
-
CRC-16/CCITT-FALSE — polynomial 0x1021, init 0xFFFF, no reflection, no final XOR. The name is the problem: at least three different algorithms are called "CRC-16/CCITT" in the wild, and they disagree. The check value over "123456789" distinguishes them: 29B1 is the right one, 2189 and 31C3 are not.
-
Because the specification says the CRC is computed over everything up to and including tag 63's own tag and length. Those four characters are constant, so including them adds nothing cryptographically — but excluding them produces a completely different value, and the result is a decoder that reports every genuine QR as corrupt.
-
Tag 01. The value 11 means static: one code printed once, reused for every customer, with the amount typed by the payer. The value 12 means dynamic: generated for a single transaction, usually with the amount already in tag 54. A static code that also fixes an amount is legal and means the payer cannot change it.
-
Both — they answer different questions. The checksum proves the string has not changed since it was generated. The findings say whether what was generated follows the specification. A payload can be faithfully transmitted and still be missing a mandatory field, and this tool reports the two separately rather than collapsing them into one verdict.
-
Yes. Drop, paste or choose a photo or screenshot and the code is read in your browser, at up to three sizes if the first attempt fails, trying both dark-on-light and light-on-dark codes. It reads the raw bytes and decodes them itself rather than trusting the QR library's text, because a payload that is not UTF-8 would otherwise come back empty. If the code turns out to hold a web link or an Indian UPI code rather than an EMVCo payload, it tells you which and does not open anything. iPhone HEIC photos do not open in every browser; a screenshot always works.
-
PayNow and SGQR, DuitNow, QRIS, PromptPay and Thai bill payment, VietQR, FPS and QR Ph, plus the registered identifiers of the international card schemes. Anything else is reported as unrecognised rather than guessed at.
-
Because a reverse-domain identifier it does not hold in its registry still names the country that registered it in its first label — ID for Indonesia, MY for Malaysia. That is a deduction from the string's shape, not knowledge of the scheme, and the label says which one you are getting.
-
You can use it to see what a code says: which scheme, which country, which account proxy, and whether the amount is fixed. It cannot tell you that the sticker on the counter belongs to the shop you are standing in. A valid checksum proves the string is intact, and a forged code can be perfectly intact.
-
Because length fields count characters and UTF-8 encodes many characters in more than one byte. A four-character Chinese merchant name declares a length of 04 and occupies twelve bytes. Both numbers are shown when they differ, because one of them is what the parser uses and the other is what the checksum sees.
Related News
You may be interested in these recent stories from our newsroom.
-
Cornelis Raised $205m to Put RISC-V Cores in the Network Fabric
The claim is five to ten points of GPU utilisation back. It is specific enough to falsify, and it depends entirely on your workload.
-
Anthropic Called Its Claude Code Limit Change a 25% Rise. It Was a 17% Cut.
A four-month boost to Claude Code's weekly limits ended on 13 September and a permanent rise replaced it. Anthropic's own correction put the...
-
OpenAI's Agents Attacked RubyGems in May. Nobody Told RubyGems.
A swarm uploaded 2,000 packages, went after user API keys and abused the documentation builder. The volunteers who closed sign-ups for four...
Method & sources
How it computes
EMVCo EMV QR Code Specification for Payment Systems, Merchant Presented Mode. The payload is flat TLV — [2-digit tag][2-digit length][value] — with nested TLV in tags 26–51, 62, 64 and 80–99, and again in 62 subtags 50–99. Tag 63 is CRC-16/CCITT-FALSE (polynomial 0x1021, init 0xFFFF, no reflection, no final XOR) computed over the payload up to and including the literal "6304", taken over UTF-8 bytes while the length fields count characters.
What this tool implements
- The checksum is CRC-16/CCITT-FALSE and is recomputed from the payload on every keystroke, never read back from tag 63. Polynomial 0x1021, initial value 0xFFFF, no input or output reflection, no final XOR; pinned by the catalogue check value 29B1 over "123456789" and asserted NOT to equal the reflected KERMIT (2189) or XMODEM (31C3) values, because all three algorithms are called "CRC-16/CCITT" in the wild.
- ⚠️ The checksum covers the payload up to and including tag 63's own tag and length — the literal characters 6304 — and the covered range is located STRUCTURALLY from the parsed tree rather than by slicing four characters off the end. The difference is the point: a payload with data after tag 63 is reported as malformed instead of being quietly checksummed over the wrong range.
- ⚠️ Length fields count CHARACTERS and the checksum counts BYTES. EMVCo's own worked example settles both at once — its alternate-language merchant name is four Chinese characters with the length field 04 and twelve UTF-8 bytes, and its stated checksum A13A only reproduces over the bytes. Five of the six published payloads in the suite are pure ASCII and agree with three different wrong readings; that one payload is the only discriminator, and both wrong combinations are asserted to fail.
- Nesting is applied by rule, never by sniffing whether a value looks like TLV. Tags 02–25 are primitive, 26–51 / 62 / 64 / 80–99 are templates, 62 subtags 50–99 are templates again, and VietQR's third level is applied only when the template's identifier is A000000727. A decoder that guesses would shred any merchant identifier beginning with two digits.
- ⚠️ "The checksum verifies" and "the payload is well formed" are reported as SEPARATE verdicts. A CRC only proves nothing changed after it was computed — a payload with no merchant account information at all can carry a perfectly correct one, and the page says so rather than letting a green checksum read as a clean bill of health.
- A scheme identifier is matched as a PREFIX of the template's subtag 00, never as a substring, so a national scheme name appearing inside some longer value cannot claim that scheme. Identifiers not in the registry are labelled "inferred" (a reverse-domain identifier names its country of registration in the first label) or "unknown", and the label is shown to the reader.
Sources
- EMVCo, EMV QR Code Specification for Payment Systems: Merchant Presented Mode: https://www.emvco.com/emv-technologies/qr-codes/ — the normative source for the data objects, their lengths, the mandatory set and the CRC. Its worked example payload (checksum A13A) is the suite's discriminating test vector.
- PayNet developer documentation, DuitNow QR merchant-presented mode examples: https://docs.developer.paynet.my/docs/duitNow-QR/integration/QR-g… — source of the DuitNow identifier A0000006150001, its acquirer/QR-ID subtags, and the sample payload with stated checksum 343F.
- Thai QR payment specification coverage, PromptPay and Bill Payment tag layout: https://thai-qr-payment.js.org/reference/spec/ — the AIDs A000000677010111 (credit transfer), A000000677010114 (one-time authorisation), A000000677010112 (domestic cross-bank bill payment) and A000000677012006 (cross-border), with their subtag meanings.
- qris-decoder reference implementation and sample payload: https://github.com/terryds/qris-decoder — source of the QRIS sample (checksum 04CB) carrying both an acquirer template at tag 26 and the national ID.CO.QRIS.WWW template at tag 51 with the NMID at subtag 02.
- vietqr-parser reference implementation and sample payloads: https://github.com/thanhtinhpas1/vietqr-parser — source of the VietQR samples (checksums CD60 and 3611) and of the three-level nesting under GUID A000000727.
- HKICL, FPS QR code specification: https://fps.hkicl.com.hk/eng/fps/merchants/qr_code.php — source of the lower-case hk.com.hkicl identifier and the sample payload with stated checksum 8D1D.
- lee-ratinan/emv-qr, a PHP EMV QR reader used as a cross-check on the root tag table: https://github.com/lee-ratinan/emv-qr — agrees on the 00/01/52/53/54/55–57/58/59/60/61/62/63/64 allocation and on 26–51 as merchant account information templates.
What can make this go out of date
- ⚠️ The scheme registry goes stale by design. A national scheme that adds or retires an identifier will not announce it here — the tool reports an unrecognised identifier as unrecognised rather than mislabelling it, which is the failure mode to prefer, but a missing entry is still a gap. Re-check the publishers named in the sources above on review.
Abridged — the full review record for this tool runs longer than the list above.
Pick up where you left off
Stored only in this browser — never sent to our servers.