Verifying issued documents
Permits, licences and certificates issued on the platform are checked every day: at roadblocks, at banks, at border posts, by other ministries. In this lesson you learn what makes a document checkable, which endpoint to use for which job, and how to report a result without ever overstating it.
What a document carries
Every issued document has:
- a verification code, random, printed under the QR code. It is deliberately not the permit number: permit numbers
are sequential, and checking by number would let anyone walk the register. On a federated deployment the code starts with the issuing node's key (for example
EXAMPLE-MDA.7KQ2-M9XZ-4A); - a QR code holding the address
<portal>/verify/<code>. When it fits, the address is followed by#and a compactsigned payload (a JWS) that can be checked offline. Browsers never send the part after
#to a server; - an electronic seal: a PAdES signature with the issuing ministry's seal certificate. Issued permits are sealed at
the long-term level (B-LTA) and carry their own validation evidence;
- a time-stamp that proves when the seal was applied;
- an entry in the platform's register of issued documents, matched by the SHA-256 of the file, with its lifecycle:
active, expired, suspended, revoked, replaced.
A document whose sealing failed is held back in a pending-seals queue; it is not issued as if it were sealed.
Choosing the endpoint
| Job | Use |
|---|---|
| A person scans a QR code | The portal page the QR opens, /verify/<code> |
| Your system checks codes or numbers | Partner API: GET /api/v1/permits/verify/{code} or GET /api/v1/verify/{documentNumber}?issuer= (scope permits:verify) |
| Your system checks many items at once | POST /api/v1/verify/documents with up to 20 items: codes (permits:verify) or base64 PDFs (documents:validate) |
| Someone uploads a PDF | POST /api/verify/document with the PDF as the body (public, 10 checks per 10 minutes per address) |
| A report to keep | POST /api/verify/document/report?format=pdf (sealed by the platform) or format=json (ETSI TS 119 102-2 shape) |
| A receipt | GET /api/receipts/verify/{ref}?sig=... |
| An older system that speaks the GSB convention | GET /api/upstream/VerifyCertificates?PermitNumber=...&DocType=... (answers true or false only) |
| No connection | The signed QR payload against a saved copy of the trust list |
The public endpoints are throttled per address for people. A system that checks many documents must use the Partner API, with its own client and limits.
curl -s "$API/permits/verify/7KQ2-M9XZ-4A" -H "Authorization: Bearer $TOKEN"
Uploaded PDFs are read in memory and never stored. Files over 10 MB or 100 pages, encrypted files and non-PDFs are refused.
Reading the answer
Every check ends in one vocabulary:
outcome:valid,warning,invalid,unknownornot-signed;headline: one plain sentence;points[]: findings with a stablecodeand a tone (ok,warn,bad,info);seal: who sealed, for which ministry, when, and whether the time is proven by a time-stamp or only claimed;changed: whether the file was altered after sealing;record: the issued record it matches, without holder data.
A code check returns the record itself: valid (true only when the status is active), status, statusLabel, validFrom, validUntil, validityText, the issuer, the service, the holder's initials and surname, a masked identifier, and lifecycle details such as supersededBy and suspendedUntil.
Seal and status, side by side
The most common mistake is to treat a genuine seal as a valid document. They are two separate facts, and the platform always reports both:
- the seal is genuine, and the document was revoked on a date for a reason;
- the seal is genuine, and the document was replaced by another number;
- the seal is genuine, and the permit expired;
- the seal certificate was revoked after sealing: the seal stays valid. Revoked before the proven signing time:
the seal is not valid;
- a back-captured paper permit is
pending-validationuntil a second officer validates it, never valid before.
The rules
- Show "valid" only when the platform said valid. Show warnings as warnings.
- Show the document's status next to the seal result, never instead of it.
- Do not invent an expiry. When there is no
validUntil, showvalidityTextas given. - A
503means "could not check now" (for example the owning node is down), not "invalid". Offer to try again. - "Not found" is one answer for every unknown, malformed or foreign code. Do not try to read meaning into it.
- Do not cache answers: they are
no-store, and a permit can be suspended at any moment. - Show no more personal data than you were given. The answer masks the holder on purpose.
The trust list and offline checks
The platform publishes the certificates that seal, time-stamp and answer for revocation at /.well-known/gsb-trust-list.json, with a detached CMS signature at .../gsb-trust-list.json.p7s. Older versions are available with ?sequence=N. Each seal entry has a kid and a public jwk, which is what an offline verifier needs:
const entry = list.entries.find((e) => e.kid === header.kid); // the JWS header names the key
if (!entry || entry.kind !== 'seal') return { ok: false };
const key = crypto.createPublicKey({ key: entry.jwk, format: 'jwk' });
const ok = crypto.verify('sha256', Buffer.from(`${h}.${p}`),
header.alg.startsWith('ES') ? { key, dsaEncoding: 'ieee-p1363' } : key, b64url(s));
// then refuse a key revoked on or before the payload's issue date (payload.i)
Verify the list's own signature against the national root before you trust it, refresh it whenever you are online, and say on screen when a result was checked offline: an offline check cannot see a suspension made after your copy of the list.
Exercise
Take a sandbox permit. Check it by code through the Partner API, then upload its PDF to the public document check. Then change one byte of the PDF and upload it again. Write down the outcome, changed and record of both answers.
Check your understanding
- Why is a document checked by a random verification code rather than by its permit number?
- Which endpoint should a bank use to check hundreds of permits a day, and why not the public one?
- What is the difference between the seal being valid and the document being valid?
- What should your screen say when the answer is
503? - What can an offline QR check prove, and what can it not prove?
- Where is the trust list published, and how do you know it is genuine?
Sign in to record your progress.
Previous: Webhooks and events Next: Signing people in: Pamodzi Pass and ZamPass