Frontend Integration
The front-end integration pattern is: your backend asks the Verifier to create an authorization request and returns a QR code plus a verification session id, your front-end renders the QR code and subscribes to a live status stream served by your own backend, and you finalize the user session once the credential has been verified.
All Verifier endpoints — including the session route GET /verification-sessions/{sessionId} and the Server-Sent Events (SSE) stream at GET /verification-sessions/{sessionId}/events — require an OIDC JWT Bearer token that must stay on your backend. Because a browser EventSource cannot send an Authorization header, your backend subscribes to the Verifier's SSE stream (or polls the session) and relays updates to the browser over your own origin.
Typical Workflow
Initiate Verification: Trigger a flow from the front-end (e.g., "Login with Credential"). The front-end calls your backend, which creates the authorization request on the Verifier.
Create the Authorization Request: Your backend sends a
POSTto/verifiers/{verifierId}/authorization-requests/qr-codewith a Bearer token. The response is a PNG image of the QR code. The verification session id is returned in theX-Verification-Session-Idresponse header, and the raw request URI inX-Authorization-Request-Uri.Display QR Code: Your backend forwards the PNG (and the session id) to the front-end, which renders it. Users scan the QR code with their Empe DID Wallet.
Subscribe for Live Status (via your backend): Once the QR code is displayed, the front-end opens a connection to a proxy route on your own origin (for example
/my-backend/verification/{sessionId}/events), which relays the Verifier's session updates as described above.Wallet Interaction & Finalize: The wallet retrieves the request, selects matching credentials, and submits a Verifiable Presentation. When the session reaches the
ResponseVerifiedstate, finalize the user session — redirect the user, show their dashboard, or grant access tokens as needed.
Creating the Request (backend)
Send the request from your backend so the Bearer token is never exposed to the browser. The body must contain exactly one of dcqlQuery or presentationDefinition. The QR-code endpoint returns a PNG (image/png), so read the metadata from the response headers rather than the body:
// Runs on your backend
const res = await fetch(
`${VERIFIER_BASE_URL}/verifiers/${verifierId}/authorization-requests/qr-code`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
dcqlQuery: {
credentials: [
{
id: 'employee_badge',
format: 'dc+sd-jwt',
meta: { vct_values: ['https://issuer.example.com/vct/employee-badge'] },
claims: [{ path: ['full_name'] }, { path: ['department'] }],
},
],
},
}),
},
);
const sessionId = res.headers.get('X-Verification-Session-Id');
const requestUri = res.headers.get('X-Authorization-Request-Uri');
const qrPng = await res.arrayBuffer(); // PNG bytes to forward to the front-endReturn the PNG bytes (or a data URL) and sessionId to your front-end. If you prefer to render your own QR code, use the JSON endpoint POST /verifiers/{verifierId}/authorization-requests instead — it returns { authorizationRequestUri, verificationSessionId, authorizationRequestId, expiresAt }.
Rendering the QR Code (front-end)
Render the PNG returned by your backend, for example as an object URL from a blob:
Relaying Session Updates (backend)
Expose a proxy route on your own backend that holds the token, subscribes to the Verifier's stream, and forwards each update to the browser. The Verifier stream emits two kinds of events:
session— carries the verification session object, including astatefield.ping— a keepalive heartbeat (data"keepalive") sent every 15 seconds. It only keeps the connection open; you can drop it or forward it as-is.
The session state transitions through these values:
RequestCreated— the request exists and is waiting to be retrieved.RequestUriRetrieved— the wallet has fetched the request and is preparing a presentation.ResponseVerified— the presentation was received and verified successfully. This is your success signal.Error— verification failed; inspecterrorMessagefor the reason.
A backend relay, for example with EventSource server-side (or any HTTP client that streams the response), subscribes with the Bearer token and re-emits updates to the browser over your own origin:
If you prefer not to keep a long-lived upstream connection open, your backend can instead poll GET /verification-sessions/{sessionId} with the Bearer token and push the state to the browser; the response carries the same state field.
Listening for Session Updates (front-end)
In the browser, open an EventSource against your own backend proxy route (not the Verifier). The browser sees the same session and ping events your backend forwards. Branch on the event type, then on the session state:
A session event is emitted immediately on subscribe at the Verifier, so once your relay forwards it your handler sees the current state right away. The session object also includes id, verifierId, authorizationRequestUri, expiresAt, and errorMessage.
Finalizing the Session
When the front-end observes ResponseVerified, it should not trust that signal alone to grant access. Instead, call your own backend, which independently confirms the result by reading GET /verification-sessions/{sessionId} (with a Bearer token) and checking that state is ResponseVerified before issuing your application's session token or redirecting the user. This keeps the trust boundary on your server.
In short: the browser only ever sees the QR code and status updates; the Bearer token, the SSE subscription, and the access decision stay on your backend.
Last updated