SMART Health Check-in is an open protocol that lets patients share health records from their personal health apps directly with healthcare providers. It uses OpenID for Verifiable Presentations (OID4VP) with end-to-end encryption, signed request objects, and support for both same-device and cross-device flows. A browser shim makes it easy to use from web applications, but the underlying protocol works from any environment that can make HTTP requests and handle JWE encryption.
This page is a visual overview. For the full specification, see the protocol README. To try it live, visit the interactive demo.
Patient is on their own device. They click a button, the page opens the picker,
they pick their health app, review and share data. The redirect return lands on the
portal page with a response_code to close the loop. The reference portal does
not offer a patient-initiated QR mode.
Authenticated clinic staff starts a check-in at a workstation. A QR code appears. The patient scans it on their phone, picks their health app, and shares data. The kiosk receives the encrypted result via long-poll.
Both flows use the same protocol stack, the same shim library, the same picker, the same wallet/source app, and the same verifier backend.
After the source app posts the encrypted response, the OID4VP direct_post.jwt response endpoint tells it what to do next:
redirect_uri: navigate there; in this demo it carries #response_code=....redirect_uri: show completion in the source app while the requester retrieves the result through its own application path.| Verifier | The healthcare provider requesting data. Controls the well_known: origin, hosts metadata and signing keys, signs Request Objects, stores encrypted responses. The browser-side shim library (smart-health-checkin) is part of the Verifier's frontend. In the demo: the relay/backend server + the portal or kiosk page. |
| Picker | An optional routing UI where the patient chooses which health app to use. Receives only the minimal bootstrap parameters and forwards them. In the demo: the check-in page. |
| Wallet / Source App | The patient's health app that holds their data. Verifies the signed Request Object, collects consent, encrypts the response, and POSTs it to the Verifier's response endpoint. In the demo: Sample Health App. |
Requests use the DCQL query language with one custom Credential Query: id: "smart-checkin" and format: "smart_health_checkin". That DCQL item carries an exact SMART Health Check-in clinical request at meta.request; clinical asks live in items[].
The OID4VP shell always has one smart-checkin Credential Query. The response is keyed by that id as
vp_token["smart-checkin"], whose value is a one-element Presentation array containing the SMART
clinical response object. If we ever split items into multiple DCQL queries or wrap the SMART response differently,
the relay, source apps, requester validation, tests, and this explainer must all change together.
Structured clinical data — insurance cards, plan benefit summaries, patient demographics, lab results, medications. Requested with selection.fhir selectors.
Forms the patient fills out — symptom checklists, disease-specific diaries, visit goals. The health app can auto-fill answers from patient-reported records and return a FHIR QuestionnaireResponse.
Cryptographically signed credentials — vaccination records, lab results with issuer signatures. The verifier can independently verify the issuer's signature. The item requests acceptable artifact media types through accept[].
A full request uses one DCQL credential query and lists clinical items inside the SMART request:
A SMART Health Check-in request is a series of expansions. A compact bootstrap URL resolves into a signed JWT, which contains the full request parameters including the ephemeral encryption key. Here's what each layer looks like:
Patient is on their own device — portal, patient app, or third-party site.
Same-device completion exists to bind the encrypted response to the verifier tab that initiated the request. The W3C Digital Credentials API will eventually route the wallet response back to that originating frame natively. Until then, this demo uses a server-mediated stand-in: the relay returns a response_code through a verifier-controlled redirect, and the originating tab exchanges that code for the encrypted payload.
request_uri. It builds a bootstrap URL with just client_id and request_uri, then opens the picker. The reference portal uses a same-tab handoff; the shim also supports a popup handoff for compatibility.
/.well-known/openid4vp-client from the verifier origin to get the jwks_uri. It fetches request_uri to get the signed Request Object JWT, verifies the signature, and extracts response_uri, dcql_query, and the ephemeral encryption key.
{ vp_token, state } as a JWE and POSTs it to response_uri. The server returns { redirect_uri: "portalPage#response_code=..." }. The wallet navigates there.
#response_code=.... In same-tab mode, the landing page resumes the requester session and redeems the response directly. In popup mode, the return page signals the opener before closing.
state matches the expected value, validates the SMART response against the original SMART request, and groups returned Artifacts by request item for the application.
Staff starts the check-in at a workstation. Patient completes it on their phone.
redirect_uri is stored for the response endpoint because the source app will not navigate back to the kiosk.
onRequestStart provides the launch_url to the app, which renders it as a QR code and copyable link. The Verifier app waits for the response through its own application path.
response_uri. Server returns { status: "ok" } (no redirect). Source app shows "Submission complete."
response_code is returned to the source app in this mode.
direct_post.jwt response endpoint completes: same-device returns a continuation redirect_uri with a fresh response_code; cross-device returns an acknowledgement and the kiosk retrieves the result through its authenticated session. Session binding and result retrieval are Verifier implementation details.
well_known: identity + signed Request Objects prove the request is controlled by the domain that owns the verifier origin./.well-known/openid4vp-client SHALL NOT be shown as trusted identity without an out-of-band allowlist.response_code to the initiating session; deferred completion requires an authenticated Verifier session authorized for that transaction.The OID4VP direct_post.jwt response endpoint tells the source app what to do immediately after posting the encrypted response. The SMART clinical request body stays limited to requested health artifacts and forms.
redirect_uri after it successfully processes the wallet POST. The source app follows it if present.import { request, completeSameDeviceRedirect, maybeHandleReturn } from 'smart-health-checkin';
const completion = await completeSameDeviceRedirect();
if (completion) renderResult(completion.response);
else await maybeHandleReturn();
function startCheckin(smartRequest) {
void request(smartRequest, {
walletUrl: 'https://picker.example.com',
wellKnownClientUrl: 'https://clinic.example.com',
flow: 'same-device', // or 'cross-device'
sameDeviceLaunch: 'replace',
onRequestStart(info) {
// info.bootstrap — { client_id, request_uri }
// info.launch_url — full URL for same-tab launch, popup, or QR
// info.transaction — implementation-specific request tracking details
}
});
}
// completion.response.credentials.coverage[0] → FHIR Coverage resource
Both flows use the same request() call. Same-device can use a same-tab or popup handoff;
cross-device renders info.launch_url as a QR code.
| Portal | Same-device demo. Calls shim with flow: 'same-device'; no patient-portal QR mode. |
| Kiosk | Staff-session-bound cross-device demo. Staff login, QR code, long-poll. flow: 'cross-device'. |
| Picker | Shared routing page. Receives bootstrap params, forwards to source app. |
| Source (Sample Health App) | Mock wallet. Fetches metadata, verifies signed Request Object, collects consent, encrypts and POSTs response. |
| Relay / Verifier | Backend. Serves .well-known, signs Request Objects, stores encrypted responses, enforces session binding for cross-device. |
Everything runs on a single port via bun run demo. The selected JSON profile in
deployments/ controls the hosted URLs and share-sheet apps.