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, a popup opens, they pick
their health app, review and share data. The popup returns to 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 request tells it which completion shape to expect:
completion: "redirect": expect a redirect_uri with #response_code=..., then navigate there.completion: "deferred": expect a simple success acknowledgement, then show completion in the source app.| 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 a custom credential format called smart_artifact. Three kinds of health data can be requested through the meta field:
Structured clinical data — insurance cards, plan benefit summaries, patient demographics, lab results, medications. Requested by canonical StructureDefinition URL.
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. Requested by combining a FHIR profile with a signingStrategy.
If signingStrategy is omitted, the wallet may return any format. If present, it lists acceptable strategies: ["shc_v1", "none"] = prefer signed, accept unsigned. ["shc_v1"] = SHALL be signed.
A full query combines these into a credentials array. Use credential_sets with required: false to mark items as optional:
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.
request_uri. It builds a bootstrap URL with just client_id and request_uri, then opens a popup to the picker.
/.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=.... The Verifier app uses that completion signal to obtain the encrypted response through its own application path.
state matches the expected value, resolves any artifact_ref inline references, and returns the credentials to the application.
Staff starts the check-in at a workstation. Patient completes it on their phone.
redirect_uri is needed 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.
completion: "redirect"; cross-device uses completion: "deferred". 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 protocol tells the source app what to do immediately after posting the encrypted response. The Verifier decides how to store, retrieve, and bind that response inside its own application.
response_code or a simple acknowledgement after it posts the encrypted response.import { request, maybeHandleReturn } from 'smart-health-checkin';
const result = await request(dcqlQuery, {
walletUrl: 'https://picker.example.com',
wellKnownClientUrl: 'https://clinic.example.com',
flow: 'same-device', // or 'cross-device'
onRequestStart(info) {
// info.bootstrap — { client_id, request_uri }
// info.launch_url — full URL for popup or QR
// info.transaction — implementation-specific request tracking details
}
});
// result.credentials['coverage-1'][0] → FHIR Coverage resource
Both flows use the same request() call. The difference is in what the app does with
onRequestStart: same-device ignores it (the shim opens the popup), 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 demo/serve-demo.ts. For local development,
./start-local.sh starts multi-origin servers to simulate real cross-origin security.