DPP Resolution: How We Wired It Up
How OpenEPCIS combined GS1 Digital Link, the IETF linkset format and content negotiation into one DPP resolution flow
What this page is
A walkthrough of one way to take a scanned QR code on a product through to a typed DPP response — the way we did it with GS1 standards. It's not a finished cross-sector product offering and it's not the canonical flow for every regulator and every customer. It's a worked example that exercises the standard primitives in a single path, so a developer or compliance reviewer can see how the pieces fit together.
The standards that show up below — GS1 Digital Link, the GS1 Conformant Resolver discovery contract, IETF application/linkset+json (RFC 9264), HTTP content negotiation, and ESPR Article 9 access tiers — are all published. The opinions in between are ours, and other implementations make different ones.
The flow at a glance
flowchart TD
A["<b>Data carrier</b><br/>QR / NFC / RFID<br/>https://id.gs1.org/01/<GTIN>/21/<serial>"]
A --> B["<b>GS1 resolver</b><br/>id.gs1.org or self-hosted<br/>looks up the linkset"]
B --> C["<b>DPP endpoint</b><br/>content negotiation +<br/>linkType routing"]
C --> D{"Access tier<br/>(ESPR Art. 9)"}
D -->|Public| E["Return data"]
D -->|AuthorizedOnly| F["Verify credential"]
F -->|Valid| E
F -->|Invalid| G["403 Forbidden"]
E --> H["<b>Response</b><br/>JSON-LD / HTML /<br/>RDF / EPCIS events"]
Walking it through with one example
The example below uses a serialised timber product — chosen because timber under EUDR exercises the access-tier surface (a customs officer needs more detail than a consumer). The same primitives apply when the product is a battery, a garment, a smartphone or a frying pan; only the linkset entries and the JSON-LD payloads change.
1 — The Digital Link URI on the carrier
The QR code carries a GS1 Digital Link URI:
https://id.gs1.org/01/09521234000020/21/LOG-2025-001
└────┬───┘ └──────┬─────┘ └──────┬─────┘
Base GTIN (AI 01) Serial (AI 21)
Application Identifiers (01, 21, …) are the same primitives GS1 has used in barcodes for decades. Encoding them in a URL means the same identifier is scannable by a phone camera, queryable by a server, and visible in a browser bar.
2 — Resolver lookup
The resolver matches the GTIN against its registry and returns an application/linkset+json document (IETF RFC 9264). One object per anchor, GS1 link relations as full-IRI keys, link descriptors as values. The discovery contract that lets downstream registries find and validate the deployment is the GS1 Conformant Resolver standard — see Resolver Setup for the full shape and the hosting choices we considered.
3 — Content negotiation
The same Digital Link URL serves different representations depending on the Accept header. A consumer's phone browser sends text/html and lands on a passport web page. A downstream system asks for application/ld+json and gets a typed JSON-LD record. RDF tooling asks for text/turtle. This is plain HTTP content negotiation, not anything GS1-specific:
| Accept header | Response |
|---|---|
text/html | Consumer-facing passport page |
application/ld+json | JSON-LD record |
application/json | Plain JSON |
text/turtle | RDF / Turtle |
4 — linkType for facet selection
Layered on top of Accept, the ?linkType= query parameter requests a specific facet of the passport. Per the GS1 Digital Link spec the value is the bare relation name (the compact gs1:… form only appears as keys inside the linkset JSON itself).
The list below is the relations our resolver auto-derives today; a deployment can extend it with sector-specific relations as needed.
?linkType= | What it returns |
|---|---|
pip | Product Information Page (default) |
epcis | EPCIS event history |
certificationInfo | Certificates and declarations |
safetyInfo | Safety data sheet |
recipeInfo | Composition / BOM |
serviceInfo | Service and repair |
productSustainabilityInfo | Carbon footprint, recycling |
5 — Access tier (ESPR Article 9)
ESPR Article 9 defines three access tiers for DPP data. The resolver enforces them at the request boundary:
flowchart TD
R[Request arrives at DPP endpoint] --> L{Data access<br/>tier}
L -->|Public| Ret[Return]
L -->|AuthorizedOnly| Auth[Verify credential<br/>or capability token]
L -->|Restricted| Agr[Check bilateral<br/>agreement]
Auth -->|Valid| Ret
Auth -->|Invalid| Den[403 Forbidden]
Agr -->|Granted| Ret
Agr -->|Not granted| Den
| Tier | Who can read it | Examples |
|---|---|---|
| Public | Anyone | Product name, manufacturer, recycling instructions, headline specs |
| AuthorizedOnly | Market surveillance, customs, accredited recyclers | Test reports, compliance dossiers, detailed supply chain |
| Restricted | Named partners under agreement | Trade secrets, proprietary formulations, full BOM |
In our implementation the Authorized and Restricted tiers reuse the same identity layer that gates the EPCIS REST surface — a Keycloak-issued credential, a capability token, or (where we've wired it up) a Verifiable Credential presented via OID4VP. See Architecture → Wallet-agnostic Verifiable Credentials.
Instance URIs vs vocabulary URIs
The URIs that identify a specific product and the URIs that define what a property means are distinct, and confusing them is a common early mistake:
- Instance URI —
https://id.gs1.org/01/09521234000020/21/LOG-2025-001resolves to this specific timber log's record. - Vocabulary URI —
https://ref.openepcis.io/extensions/eu/eudr/commodityTypeis a definition of thecommodityTypeterm and resolves to documentation.
In a JSON-LD document the @id is an instance URI; predicates (battery:stateOfHealth, eudr:commodityType, …) expand to vocabulary URIs through the @context.
What this walkthrough is not
A few things this page deliberately doesn't claim:
- It isn't a finished product offering. We've built the resolution mechanism and exercised it; the per-sector DPP credentials and the regulator-side acceptance criteria still depend on the underlying regulation landing (ESPR delegated acts, EU Central DPP Registry, sector EN standards). Status notes live on the per-domain implementation pages.
- It isn't the only valid resolution flow. Other implementations put the resolver on a brand-owned domain, redirect rather than serve, gate everything behind authentication, fall back to a static page, or short-circuit content negotiation entirely. Each makes different trade-offs.
- It isn't sector-specific. The primitives — Digital Link URI, linkset, content negotiation, linkType, access tier — are sector-neutral. The per-sector work is which fields go in the JSON-LD payload, not how the URL gets resolved.
Next steps
- Resolver Setup Guide — hosting options and the linkset JSON shape.
- Battery DPP Implementation, EUDR Implementation, Textile DPP, Electronics DPP — per-sector linksets and event types.
- Architecture → Wallet-agnostic Verifiable Credentials — how access-tier enforcement plugs into the OID4VC layer when a presented credential is part of the request.