Physical identification, QR and NFC

Choose the physical carrier for your products, learn the exact form of the GS1 Digital Link, and know what each carrier requires before you go into production.

On this page

A physical product has to carry something a phone can read. This page gives you the two carriers available, the printed QR and the NFC seal. When you leave it, you will know which exact address is printed or written into the chip, what the server answers when you read it, what each carrier brings at scan time, and the work each one requires before you launch a production run.

The ESPR regulation allows the passport at three levels: the model, the batch and the item. The delegated act for your product group decides which one applies. The address forms described below cover all three. A model passport covers every item that shares the same product code, a batch passport every item of one batch.

Here is the full path, from the physical carrier to the page the person scanning sees. The rest of this page covers each step in detail.

The path of a scan, from the physical carrier to the public page

Two separate columns, one per carrier, joining at the bottom. On the left, the QR printed on the label carries a fixed address, always the same one, sealtrust.io/p/0ABCDEFGHJKM, or its GS1 form sealtrust.io/01/{gtin}/21/{serial}. The resolver normalizes the number, finds the unit, then answers with a single 302 redirect: to the product page by default, to the passport page with the linkType=dpp parameter. On the right, the NFC seal, an NTAG 424 DNA chip, fills in the sealtrust.io/verify address itself with the e and c parameters, and that address changes on every read. The /verify page then calls GET /sdm/verify-url, the chip signature and its counter are checked, then the page shows a verdict followed by the record: product, brand, history, read counter. Both columns end at a public web page on the site, with no app to install, which answers the questions a scan raises and points to the passport. The QR identifies the object, the NFC seal adds a signature computed by the chip, different on every read.

A brand, or its reseller, can show its passports in a partner's page. When it has switched this on, the QR leads to the partner's page, and the /verify page, once the chip is verified, sends the visitor there with a receipt of that verification: the chip is verified only once. See Show the passport in your page.

#The three possible combinations

Every product carries an authentication method value. The product stores one of three.

ValueWhat the product carries
qrA printed code alone.
nfcAn NFC seal alone.
nfc+qrBoth on the same product.

You choose this value when you create the product, in the console form or in its CSV import. Always fill it in. A missing value counts as nfc. The server then refuses the creation with a 403 if your plan only allows QR. For a QR-only product, write qr explicitly.

The server also accepts the spelling nfc,qr and uppercase. Write exactly one of the three values in the table, in one of these spellings. Any other value does not give you the carrier you are asking for.

Asking for a value that contains nfc requires your plan to allow NFC authentication. Otherwise the server answers a 403 carrying the code AUTH_METHOD_NOT_ALLOWED, with the method requested and the list of the ones your plan allows. A qr request alone does not require that permission.

#The printed QR

#What it encodes

The QR printed on a unit encodes a single address, in this form:

Texte
https://sealtrust.io/p/0ABCDEFGHJKM

The last segment is the unit's public serial number. This form /p/{serial} is the unique product identifier intended for the European registry, and it is the one the label carries. Code generation refuses to produce an image for a unit that has no serial number.

#What happens when it is scanned

Three readers can read this label: the phone's camera app, which simply opens the address, the iOS application, and the reader built into the site's verification page.

That site reader only accepts SealTrust addresses, and the restriction is not a formality: a QR code is text written by a stranger, and following its content without checking it would let someone stick a label on a product to send a customer to a phishing site in the brand's colours. The accepted forms are the ones we generate: /p/{serial}, /product/{serial}, /01/{gtin}, /01/{gtin}/21/{serial}, /passport/01/{gtin}, /verify, /certificate/{number} and /claim, with or without a language prefix.

The site reader also needs the camera. The page allows it for itself and for itself only: no embedded third-party frame can request it through our pages.

#The serial number

The number is twelve characters long. Its alphabet has thirty-two characters: the digits 0 to 9 and the letters A to Z minus I, L, O and U. We remove those four letters because they are confused with digits on a label.

Three practical consequences:

  • The server corrects a number copied by hand. It reads an I and an L as a 1, and an O as a 0.
  • Case does not matter. The server uppercases the number before any lookup.
  • A number that is not the right length, or that contains a character outside this alphabet, gets an immediate 404.

We draw the number at random. It does not follow production order and therefore reveals neither your volumes nor how long you have been operating.

#What happens on a scan

A phone camera opens the address. The server answers with a single 302 redirect to the product page, on the public site.

curl
curl -sS -i "https://sealtrust.io/p/0ABCDEFGHJKM"
HTTP
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/product/0ABCDEFGHJKM

The server picks the page language from the reader's Accept-Language header, between French and English. A reader asking for neither is served the language declared for that brand when they scan on that brand's domain, and English otherwise. There is never more than one redirect hop.

The product page answers the questions a person asks when scanning: what the object is, what the reading proves, who holds it, where the proof is. It points to the digital passport.

It opens with a verdict, in a box at the top of the page, that says only what this page can prove. It says "Registered number": the number does exist at the brand, but a QR code can be copied, so on its own it does not prove that the object in hand is the original. Only reading the chip, on the verification page, proves the object. On the product page, a revoked or expired certificate is announced in the same place. The passport page opens with the same box; for a model or lot passport, it says so too: it describes every item, not the one you hold.

On the passport page, a section with no field filled in is not shown. A passport published from a template without completing anything therefore shows no empty headings: it shows only what the brand actually declared. When it can, a folded section shows a short summary of its first values, so the reader knows what it holds before opening it. The technical details of the proof (fingerprint, IPFS copy, anchoring on Base) are grouped at the end, under "What this page proves, and what it does not".

When our service does not answer in time, the product page, the passport page and the certificate page say so: "Verification temporarily unavailable", with a button to try again. They say "not found" only when our service answered that there is nothing at that address. An outage says nothing about the object, and a visitor must never read that a genuine product does not exist.

The product page also offers the action that follows the reading, and the passport page offers it the same way. While the unit is still in the brand's vault, the page offers to claim it, and the button leads straight to the claim screen: there is no address to guess, and the serial number travels in the link, so nobody types it again. It is the serial printed on the product, even when the page was opened by an address that carries the chip fingerprint or the token identifier: the claim accepts the serial and nothing else. A unit the person already holds is offered for transfer. A unit that belongs to somebody else is offered nothing, and the page says so rather than leaving a button that can only refuse.

On your own domain, claiming finishes on SealTrust, and the button says so before the click. Your verification, passport and certificate pages stay entirely with you: none of them needs an account. Claiming or transferring does need one, and an account lives on SealTrust. Doing it elsewhere would force your customers to sign in again on every domain, stop their password manager from working, and above all teach them to type a SealTrust password on a domain that is not ours, which is exactly the reflex phishing exploits. The serial number follows your customer all the way: if they have to sign in, or create their account and confirm their address, they come back to the claim screen of this product, then a "Back to" button takes them to the page of your domain they left.

The holder is read from the copy of the chain our watcher keeps, not from a stored email address: a unit that has just been minted carries the address of whoever minted it, and reading that would make every product leaving the workshop look "already claimed".

A unit is claimed by one person at a time. Once someone has started claiming it by its serial number, it stays reserved for at most ten minutes while they finish. Anyone else who tries in the meantime reads "a claim for this product is already in progress, try again in a few minutes", and the API answers 409 with the code CLAIM_IN_PROGRESS and a Retry-After header giving the wait in seconds. The first person's ticket is never handed to the second one: a serial number is printed on the item, so it does not prove that you hold it.

#Requesting the passport with the linkType parameter

A machine reader that wants the document itself adds a linkType parameter.

curl
curl -sS -i "https://sealtrust.io/p/0ABCDEFGHJKM?linkType=dpp"
HTTP
HTTP/1.1 302 Found
Location: https://sealtrust.io/en/passport/<item identifier>

The last segment is the unit's hash, failing that its token id, failing that its serial number.

Five values are recognized: dpp, passport, gs1:dpp, gs1:digitalproductpassport and digitalproductpassport. Any other value is treated as if the parameter were absent, and the reader lands on the product page.

A unit withdrawn from circulation always goes to its passport, even without this parameter. Its identifier keeps answering, which the standard requires.

#The exact form

The same product is addressable through a GS1 Digital Link. Its form is set by the path structure.

Texte
https://sealtrust.io/01/03701234567891/21/0ABCDEFGHJKM
  • 01 is the application identifier for the GTIN, the international article number.
  • 03701234567891 is the GTIN normalized to fourteen digits.
  • 21 is the application identifier for the serial number.
  • 0ABCDEFGHJKM is the same public serial number as in the short form.

Both forms designate the same unit and lead to the same page. The domain does not make the GS1 Digital Link, the path structure does. A resolver hosted on your own domain stays compliant.

Serving these resolvers on your own domain name is part of the SealTrust Inside plan. Without it, your links stay on sealtrust.io.

#What the server checks

curl
curl -sS -i "https://sealtrust.io/01/03701234567891/21/0ABCDEFGHJKM"
HTTP
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/product/0ABCDEFGHJKM

The GTIN in the path must match the GTIN of the model the unit belongs to. Otherwise the server answers a 404. A unit whose model carries no GTIN is therefore not addressable through this form, and stays addressable through the short form /p/{serial}.

A GTIN that cannot be normalized gets a 400 with the message Invalid GTIN. The server keeps the digits of the segment, refuses anything beyond fourteen, and left-pads with zeros up to fourteen. The GTIN-8, GTIN-12, GTIN-13 and GTIN-14 forms therefore pass, with or without separators.

It then checks the GS1 check digit, and answers 400 with the message Invalid GTIN: the check digit does not match. when the last digit does not match the ones before it. This check happens at resolution time, and no longer only when the model is recorded: a miscopied code is therefore named as such, instead of answering "unknown product" and sending the reader to look in the wrong place.

Every other dead end gets the same 404 with the same message. An unknown number and a number known under another GTIN are indistinguishable from the outside.

#The form without a serial number

A link without the 21 segment designates a commercial model. It designates no particular unit.

curl
curl -sS -i "https://sealtrust.io/01/03701234567891"
HTTP
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/passport/01/03701234567891

It resolves to the model-level passport, the one attached to a model and to no unit. The server never serves a passport attached to a unit here: the data of one item must not be shown for another item of the same model.

The server answers a 404 in four cases, without distinguishing them: the GTIN is unknown, no public model passport is published for that GTIN, several brands publish a model passport under the same GTIN, or the GTIN belongs to a brand other than the one that owns the domain the scan arrives on.

SealTrust no longer generates a QR code on this link: each piece carries the QR code of its unit, whose page shows this same passport. A code already printed on this link keeps resolving.

The batch level has its own address form: the GTIN of the model, then the GS1 application identifier 10, then the lot number as it is recorded on the batch, case included.

HTTP
GET https://sealtrust.io/01/03701234567891/10/LOT-26A
HTTP
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/passport/01/03701234567891/10/LOT-26A

It resolves to the passport of the batch when that batch has one published. Otherwise it resolves to the passport of the model, as /01/{gtin} does: a label printed before the batch passport was published keeps answering. The lot number is at most twenty characters, taken from the GS1 character set, without a slash or a % sign. The console refuses to create the passport of a batch whose number would not fit in that link. See GET /01/{gtin}/10/{lot}.

SealTrust no longer generates a QR code on this link: each item of the batch carries the QR code of its unit, whose page shows the batch's passport. A code already printed on this link keeps resolving.

#Identifier length, and the submission channel that sets it

The European registry does not apply one limit, it applies two, depending on how the registration reaches it.

Submission channelAccepted length
Registry web form50 characters at most, a cap planned to be increased
File upload, XML or JSON8 to 2,000 characters

The Commission's help desk stated this in writing on August 31, 2026. The same answer confirms that a compressed GS1 Digital Link is an acceptable form of identifier. That answer is not an official text: if the registry guide ever says otherwise, the guide prevails.

Your brand files with the registry itself, as a verified economic operator: the registry does not accept today that a third party registers in your place. SealTrust prepares the file, in JSON or in XML, and you upload it. So the file bounds are the ones that apply, and a full GS1 link fits inside them without difficulty.

To check that an identifier answers, the registry follows at most three redirects, and refuses a local or unsafe address. Our identifiers reach their page in a single redirect.

The calculation below keeps its value for the form channel, and for the readability of the printed code. The GS1 structure consumes twenty-two characters on its own, on top of the domain: /01/, fourteen GTIN digits, /21/. The domain you choose therefore decides how many characters are left for the serial number.

Resolver domainCharacters left for the number
https://id.gs1.org10
https://sealtrust.io8
https://api.sealtrust.io4

Take the shortest domain you have. An api. subdomain is a trap. The identifier intended for the registry is the short form /p/{serial}, which does not spend those twenty-two characters.

The published passport carries both values: the normalized GTIN in the gtin field, and the full link in the gs1_digital_link field.

#What QR alone gives you

  • It carries the identifier intended for the registry. The /p/{serial} form is the unique product identifier the European registry expects. The field that must serve the document carries the same address followed by ?linkType=dpp.
  • It shows the passport of the model or the batch. Each piece carries its own code, and its page shows the passport of its batch, or else of its model. The shared links /01/{gtin} and /01/{gtin}/10/{lot} already printed keep resolving.
  • It requires no hardware. The code is an image you download and send to print.
  • It leads to an ordinary web page. No app is needed to read it.

What it does not do: a printed QR encodes a fixed address. A photograph of the label encodes the same address. The resolver looks up the serial number and redirects; it has nothing else to examine. The QR therefore identifies an object, and does not by itself prove that the label has not been copied.

#What the NFC seal adds

The NFC seal is an NTAG 424 DNA chip. On every read, it writes two parameters into the address itself: an encrypted piece of data and a verification seal. The address therefore changes on every read.

Texte
https://sealtrust.io/verify?e=<encrypted data>&c=<seal>

The e parameter is thirty-two hexadecimal characters long. The c parameter is sixteen. A phone brought near the chip opens that already-completed address. The /verify page then queries the API verification endpoint, on api.sealtrust.io.

On the SealTrust Inside plan, we burn your own domain name into the chip, in place of sealtrust.io. That choice freezes at the first encoding: once a chip is burned, the server refuses any domain change with a 409, because chips already applied would point nowhere.

Three things follow.

An address already used is refused. The chip increments a counter on every read. The server requires that counter to advance strictly. An address captured then replayed gets a 409. A signature that does not match gets a 403 with the code SDM_MAC_MISMATCH. Malformed parameters get a 422.

The opening seal, on chips that carry one. Some chips embed a wire that breaks when the package is opened. They then add a two-character t parameter, placed between e and c. The response exposes three fields:

FieldContent
tamper_statusThe two raw characters: CC never opened, OO opened, OC opened then closed again, II function never activated.
seal_intacttrue never opened, false opened, null nothing to say.
tamper_policyWhat your brand wants done with it.

The seal_intact field has three states. The value null does not mean broken: an ordinary chip and a chip whose function was never activated both give null. Showing it as an alert would put a counterfeit warning on genuine products.

A richer response than the QR redirect. Reading a chip returns a document directly, with the product name, the brand, the token id, the contract address, the history, the counter value, a declared_stolen flag if a theft report is open on the unit, and a scan area at city granularity when the location is provided.

#What each one costs in preparation

#The QR

You request the image when you need it, and the server returns it as PNG. You choose a size between 100 and 1000 pixels. Error correction is level M and the white margin is two modules. Keep that margin when printing, otherwise reading becomes unreliable.

You can download the code for a product, the code for a certificate, or the ZIP archive of every code in a batch at once.

A brand on the Compliance + Identity offer prints the code of every unit. Asking for the code of a model or a batch answers it 410 with the code ITEM_QR_ONLY: it prints each piece's code, whose page shows the passport of its batch or of its model.

A brand on the Compliance offer has no units: it prints the code of its model, GET /qr/01/{gtin}, or of its batch, GET /qr/01/{gtin}/10/{lot}, once that passport is published. See Compliance, or Compliance + Identity.

In short: no hardware, no per-item handling, no procurement lead time.

#The NFC seal

Preparation is of a different nature.

  1. Your plan must include NFC authentication. Otherwise the server refuses product creation with a 403.
  2. You source the chips. They are physical components, with a lead time and a cost per item.
  3. Every chip passes in front of a reader before the product is created. We read its hardware identifier, you associate it with a line of your batch, you create the products, we encode the chips. It is a four-step path, and it happens chip by chip.
  4. We write the address into the chip at encoding time. You cannot change it remotely afterwards. A badly encoded chip is a chip to replace physically.

On an NFC batch, you can produce a printed QR in parallel for the same units. The reverse does not work: a chip address printed into a QR does not work, because it is the chip that fills in the e and c parameters, and a printed image fills in nothing.

#Choosing

QuestionQR aloneNFC seal
/p/{serial} identifier intended for the European registryYesYes
Page of the piece showing the passport of its batch or modelYesYes
Hardware to sourceNoYes
Per-item handlingNoYes
Reading without an appYesYes
A different address on every readNoYes
Replay of a captured address refusedNoYes
Package opening detectionNoOn chips that carry one
Correction possible after manufacturingReprint the labelReplace the chip

One last point about domains. If you serve these resolvers on your own domain name, a link stays on that domain from start to finish, and your customers never see ours. A brand domain resolves only that brand's products: another brand's code scanned on your domain gets a 404. The same rule holds for reading an NFC chip, through the scan page as through our apps: another brand's chip read on your domain gets a 404. The refusal comes before the chip's counter moves and before any read is recorded. A chip that matches no product gets the same 404 on your domain.

Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.

Suggest a correctionReport a problem