# 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.

Source: https://docs.sealtrust.io/en/identification-physique/

---

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 two address forms described below cover the model and the item. A
model passport covers every item that shares the same product code.

> [!INFO] QR alone is a mode in its own right
> The printed QR carries the unique product identifier, the one the European
> digital product passport registry expects. It is the data carrier the standard
> provides for. The NFC seal adds a layer on top, for brands that need one.

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.

:::schema chemin-du-scan
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.
:::

## The three possible combinations

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

| Value | What the product carries |
| --- | --- |
| `qr` | A printed code alone. |
| `nfc` | An NFC seal alone. |
| `nfc+qr` | Both 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:

```text
https://sealtrust.io/p/0ABCDEFGHJKM
```

The last segment is the unit's public serial number. This form
[`/p/{serial}`](/en/reference/get-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.

### 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.

```bash
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, French by default. There is never more than one
redirect hop.

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

### Requesting the passport with the `linkType` parameter

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

```bash
curl -sS -i "https://sealtrust.io/p/0ABCDEFGHJKM?linkType=dpp"
```

```http
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/passport/<identifiant de l'unité>
```

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 GS1 Digital Link

### The exact form

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

```text
https://sealtrust.io/01/03701234567890/21/0ABCDEFGHJKM
```

- `01` is the application identifier for the GTIN, the international article
  number.
- `03701234567890` 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`.

> [!ATTENTION] The serial number in the link is not the token id
> The token id on the chain is a number of seventy-seven to seventy-eight
> digits. It does not fit in an address and is not what the resolver expects.
> The `21` segment carries the twelve-character public serial number.

### What the server checks

```bash
curl -sS -i "https://sealtrust.io/01/03701234567890/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.

```bash
curl -sS -i "https://sealtrust.io/01/03701234567890"
```

```http
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/passport/01/03701234567890
```

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.

This is the form that suits anything sold by the meter, by the box or by the
pallet. A single code, printed on every unit of the model, and nothing created
per item.

The batch level has no address form of its own. A passport attaches either to a
model or to an item. The printed code is therefore the model's or the item's.

### The fifty-character limit

The European registry caps the unique product identifier at fifty characters.
The GS1 structure consumes twenty-two 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 domain | Characters left for the number |
| --- | --- |
| `https://id.gs1.org` | 10 |
| `https://sealtrust.io` | 8 |
| `https://api.sealtrust.io` | 4 |

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 works at model level.** A single code for a whole model, with nothing
  created per item, using the [`/01/{gtin}`](/en/reference/get-gs1-gtin/) form.
  That level serves the passport and can be scanned.
- **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.

> [!ATTENTION] We do not file on your behalf
> We produce the declaration file in the Commission's official format. You, the
> economic operator, are the one who enrolls it with your qualified seal. The
> European registry today accepts only the battery group, and the file covers
> only the serialized item `/p/{serial}`. The model level `/01/{gtin}` serves
> the passport and can be scanned, with no path to the registry as of today.

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.

```text
https://sealtrust.io/verify?e=<donnée chiffrée>&c=<sceau>
```

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:

| Field | Content |
| --- | --- |
| `tamper_status` | The two raw characters: `CC` never opened, `OO` opened, `OC` opened then closed again, `II` function never activated. |
| `seal_intact` | `true` never opened, `false` opened, `null` nothing to say. |
| `tamper_policy` | What 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.

> [!ATTENTION] A broken seal is not a counterfeit
> A customer opening the box breaks the seal legitimately, and every secondhand
> product has broken it. That is why the brand's default policy is `info`, which
> shows the state without judgment. The two other values are `off`, which shows
> nothing, and `gate`, which presents a broken seal as a warning. The
> authenticity verdict does not change in any of the three cases.

**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.

For a model-level QR, the console refuses to generate the image as long as no
public model passport is published for that GTIN. The message is explicit.
Printing a code that leads nowhere costs more than waiting.

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.

> [!ATTENTION] Encoding depends on your plan
> Physical minting and chip encoding are run from the console, on a workstation
> fitted with a contactless reader. If your plan does not include NFC encoding,
> the console tells you that physical tag encoding is handled by SealTrust, and
> you follow the progress of minting and encoding from the batch page.

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

| Question | QR alone | NFC seal |
| --- | --- | --- |
| `/p/{serial}` identifier intended for the European registry | Yes | Yes |
| Model level, one code for a whole model | Yes | Not applicable |
| Hardware to source | No | Yes |
| Per-item handling | No | Yes |
| Reading without an app | Yes | Yes |
| A different address on every read | No | Yes |
| Replay of a captured address refused | No | Yes |
| Package opening detection | No | On chips that carry one |
| Correction possible after manufacturing | Reprint the label | Replace 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`.
