LHDN MyInvois Digital Signature & UUID Verification: Ensuring Tax Compliance
Verify a Malaysian e-Invoice through its MyInvois UUID, long ID, validation link and QR code—and distinguish that evidence from a document hash or digital signature.
TL;DR (Key Takeaways)
- •A MyInvois UUID identifies one accepted document; a submissionUid identifies the batch that carried it. Neither value alone proves that the document later reached Valid status.
- •For a public validation link, LHDN's SDK specifies the MyInvois portal base URL followed by /{uuid}/share/{longId}; the long ID is returned for a valid document and the exact link may be encoded as a QR code.
- •A QR image is only a transport mechanism. Verification means opening the official MyInvois validation destination and matching the returned document identity, parties, amount and current status to the invoice received.
- •GetPay currently captures the accepted UUID, stores the submission UID, reads status and stores longId when returned. Its present invoice PDF does not embed a MyInvois QR code, and its UBL mapper does not create a digital-signature block.
What does a MyInvois UUID actually prove?
A MyInvois UUID is the tax authority's identifier for one submitted e-Invoice document. It is not the supplier's invoice number, not the submission batch identifier, and not a declaration that validation has finished.
GetPay sends the tenant-side invoice number as codeNumber. When MyInvois accepts the document into a submission, the response is expected to separate:
| Field | Scope | What it is used for |
|---|---|---|
invoiceCodeNumber | Supplier's document | Maps the response back to the supplier's invoice number |
uuid | One MyInvois document | Identifies the individual e-Invoice in later reads and state changes |
submissionUid | Submission batch | Identifies the API submission that contained one or more documents |
longId | Public validation access | Combines with the UUID and portal base URL to form the validation link |
GetPay persists acceptedDocuments[0].uuid as lhdn_uuid and submissionUid as lhdn_submission_id. The initial local state becomes PENDING, not VALID. That distinction matters: acceptance into processing and successful validation are two different events.
The document-details read recognises four remote lifecycle values: Submitted, Valid, Invalid, and Cancelled. GetPay maps them to PENDING, VALID, INVALID, and CANCELLED. An unknown value throws instead of being silently treated as compliant.
Use this evidence ladder:
- No UUID: there is no locally captured proof that MyInvois accepted this document.
- UUID plus
PENDING: MyInvois accepted it for processing, but validation is not complete. - UUID plus
INVALID: the document failed validation; inspect the returned validation error. - UUID plus
VALID: MyInvois reports the document as valid. - UUID plus
CANCELLED: the identifier remains useful for history, but the document is no longer an active valid invoice.
How do UUID and longId become a validation link?
LHDN's MyInvois SDK gives the public validation-link pattern as:
{myinvois-portal-base-url}/{uuid}/share/{longId}
Use the portal base URL for the correct environment. Do not copy a sandbox host into a production invoice, and do not invent a link from the API host used for authenticated server calls. The SDK describes longId as a long temporary identifier for anonymous document access and states that it is returned for valid documents.
In GetPay, the first submit response type captures the UUID but not longId. The later document-details type contains both:
export interface DocumentDetails {
uuid: string
longId?: string
status: "Submitted" | "Valid" | "Invalid" | "Cancelled"
validationResults?: {
status: "Valid" | "Invalid"
validationSteps?: Array<{
name: string
status: string
error?: { code: string; message: string }
}>
}
invalidReason?: string
}
The authenticated status route reads details using the stored UUID. If longId is present and differs from the local value, it writes the returned value to lhdn_long_id. It also records when the document first becomes valid and preserves an invalid reason or validation-step error when supplied.
That sequence avoids a common implementation error: generating a public link immediately from the UUID alone. The correct implementation waits until MyInvois returns the long ID, retains the UUID and long ID as separate values, and builds the validation link only from the correct portal environment.
What does a MyInvois QR code verify?
A QR code does not perform tax validation. It encodes the validation link so a phone or scanner can open it without retyping a long URL. The verification happens when the user reaches the official MyInvois destination and checks the authority's record.
For an invoice issuer, a defensible QR workflow is:
- Wait until the document status is
Valid. - Obtain the returned
uuidandlongId. - Construct the validation link with the documented portal pattern.
- Generate a QR code from that exact link without shortening or redirecting it through a third-party domain.
- Decode the finished QR in a test step and compare the decoded text byte-for-byte with the intended link.
- Open it in the correct environment and verify that the expected document is shown.
- Place the tested QR on the customer-facing PDF without covering invoice totals, supplier details, or statutory fields.
The decode-after-render step catches practical failures that API tests miss: a truncated URL, a sandbox link on a production invoice, insufficient contrast, a QR rendered too small, or a PDF layout that clips the quiet area around the code.
For a buyer receiving an e-Invoice:
- Scan the QR with the phone camera or a trusted scanner.
- Inspect the destination before proceeding. It should be the official MyInvois portal, not a look-alike domain or an unrelated URL shortener.
- Open the validation record.
- Compare the invoice number, issuer and receiver identifiers, issue date, currency, total and current status wherever those values are presented.
- Reject the match if the record belongs to another document, remains unavailable, is invalid, or is cancelled.
- Keep the supplier's invoice and the verification evidence according to the organisation's record-retention procedure.
Scanning successfully is not the same as matching successfully. A genuine QR copied from another valid invoice can still be placed on a false PDF. The buyer must compare the record to the document in hand.
Is documentHash a digital signature?
No. A hash, a digital signature, and a MyInvois validation link solve different problems.
GetPay's buildSubmissionDocument() stringifies the completed UBL JSON once, calculates SHA-256 over those exact UTF-8 bytes, and Base64-encodes the same string:
const canonical = JSON.stringify(ublDoc)
const bytes = new TextEncoder().encode(canonical)
const hashBuf = await crypto.subtle.digest("SHA-256", bytes)
const documentHash = [...new Uint8Array(hashBuf)]
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("")
const document = Buffer.from(canonical, "utf8").toString("base64")
This proves that the submitted documentHash describes the submitted document bytes. It does not prove who created those bytes because no private key signed the digest.
LHDN's signature guidance describes a different mechanism for signed UBL documents. At a high level, the document carries a signature structure, certificate information, digests and a signature value. MyInvois validation checks the signature structure, certificate validity at submission, the relationship between the certificate holder and the issuer or authorised intermediary, and the permitted Malaysian certification authority requirements.
The practical comparison is:
| Mechanism | Main question answered | Evidence |
|---|---|---|
SHA-256 documentHash | Did the digest come from these exact transmitted bytes? | Recomputed digest equals the envelope value |
| UBL digital signature | Was the document signed with qualifying certificate material, and does the signature validate? | MyInvois signature-validation result |
| UUID plus status | Which MyInvois document is this, and what is its lifecycle state? | Authenticated API result or official validation page |
UUID plus longId link | Can the recipient open the public validation record? | Official MyInvois validation destination |
| QR code | Can the validation link be transferred accurately to a scanner? | Decoded QR equals the intended link |
GetPay's ordinary invoice mapper declares document type 01 with listVersionID: "1.1", but its current UBL object contains no signature extension or signature value. The code therefore must not be described as creating or independently verifying an LHDN invoice cryptographic signature. It creates the transport hash and relies on MyInvois for the returned validation lifecycle.
How should an API integration verify a submitted document?
Keep the internal invoice ID, supplier invoice number, submission UID, MyInvois UUID, long ID, status and validation error in distinct database fields. Collapsing them into a single “reference” column makes incident response and buyer support unnecessarily ambiguous.
GetPay's current read path is equivalent to:
const details = await getDocumentDetails(creds, invoice.lhdn_uuid)
const mapped = mapLhdnStatus(details.status)
const patch = { lhdn_status: mapped }
if (details.longId) patch.lhdn_long_id = details.longId
On Invalid, the route first uses invalidReason; if absent, it looks for a validation step containing an error and preserves that error message. On Valid, it records a local validation timestamp. The audit event records the UUID, previous status, new status and returned long ID.
For a production integration, apply these controls:
- query with the UUID returned for that exact accepted document;
- keep sandbox and production credentials, API hosts and portal links separate;
- treat remote status as authoritative rather than deriving validity from the age of a submission;
- use the validation results for evidence, not a locally invented error catalogue;
- avoid using document-detail reads as a high-frequency reconciliation loop—follow LHDN's published guidance for the appropriate submission-status operation and polling pattern;
- never automatically replay a timed-out submission POST, because MyInvois may have accepted the document even when the response was lost; and
- prevent ordinary invoice edits after a UUID has been attached, because changing the local PDF without a corresponding statutory correction breaks the match to the submitted record.
GetPay implements the last control in its invoice update route: once lhdn_uuid exists, it returns a conflict response rather than modifying the invoice. Credit, debit and refund-note flows retain the original MyInvois UUID in their billing reference instead of rewriting the accepted invoice.
What should you check during an e-Invoice compliance review?
Review the complete chain, not only the presence of a QR image:
- The supplier's invoice number maps to the correct accepted-document response.
submissionUidanduuidare stored separately.- The UUID belongs to the invoice being reviewed.
- The current MyInvois status is
Valid, not merelySubmitted. - The returned long ID is stored only from a real MyInvois response.
- The validation link uses the correct official portal environment.
- The QR decodes to that exact link after PDF rendering.
- The validation record matches the invoice parties, date, currency and total.
- A cancelled or invalid document is not represented as currently valid.
- The envelope hash is not labelled as a digital signature.
- Any digital-signature claim is supported by the signed UBL structure and MyInvois validation evidence.
- The original submitted payload, authority identifiers, lifecycle changes and correction documents remain auditable.
In GetPay today, the UUID capture, status mapping, validation-error preservation and longId persistence are implemented. QR embedding on the generated invoice PDF and signed-UBL creation are not. Keeping that boundary explicit is part of compliance: software documentation should never claim stronger verification than the evidence its code actually produces.
Frequently Asked Questions
How do I validate a MyInvois QR code?
Scan the code, confirm that the destination is the official MyInvois portal, open the validation page, and compare the returned document identity, supplier, buyer, invoice number, total and current status with the invoice you received. A QR image that merely contains text or opens an unrelated domain is not MyInvois verification.
Is a MyInvois UUID proof that an e-Invoice is valid?
No. A UUID shows that MyInvois assigned an identifier to an accepted document, but asynchronous validation may still leave it Submitted or make it Invalid. Treat the current MyInvois status as decisive; a cancelled document also must not be treated as an active valid invoice.
What is the difference between UUID, submissionUid and longId?
The UUID identifies one MyInvois document, submissionUid identifies the submission batch, and longId is the long temporary identifier used with the UUID to construct the public validation link. Keep all three in separate fields because they are not interchangeable.
Is documentHash the LHDN invoice cryptographic signature?
No. GetPay's documentHash is a SHA-256 digest of the exact JSON bytes placed in the submission envelope. A digital signature additionally requires signing material and a signature structure tied to an approved certificate; a digest by itself does not authenticate the signer.
Does GetPay currently put the MyInvois QR code on its invoice PDF?
Not in the current repository implementation. GetPay stores the accepted UUID and later stores longId when the details response supplies it, but the invoice PDF component does not receive those fields or render a QR image. This article therefore describes the compliant mechanism without claiming that PDF feature exists today.
Sources & Ground Truth
Ready to automate your Malaysian e-invoicing & bookkeeping?
GetPay handles 100% compliant e-invoices, multi-bank reconciliation, and statutory payroll out of the box.
Related Articles
The Complete Malaysian SME Accounting & LHDN e-Invoicing Blueprint 2026
A practical 2026 blueprint for Malaysian SME accounting, MyInvois e-Invoicing, statutory payroll journals, net-settlement reconciliation, SST and entity controls.
Multi-Currency Invoicing & Foreign Exchange Gain/Loss Double-Entry Journaling
A Malaysia-focused guide to multi-currency invoices, realized and unrealized foreign exchange gains or losses, month-end revaluation, settlement journals, and MyInvois currency fields.
Form EA & Form E Generation: Employer Year-End Statutory Payroll Reporting
A practical Malaysian employer guide to Form EA, e-CP8D and e-E: close payroll, separate employees from contractors, reconcile annual totals, generate reports and file in the correct order.