Getting started
This guide takes you from nothing to a working check-in on a page you own: a patient portal, a kiosk screen, a link you text people before their visit.
What it does
A check-in is a short exchange between your page and the patient's health app. Your page sends a request that names the things the visit needs: an insurance card, a medication list, a questionnaire. The browser passes that request to the health app — the protocol calls the app a wallet. The patient sees each item and chooses what to share. The app sends back the data it agreed to share, encrypted so that only your page can read it, and your page receives it as ordinary JavaScript objects.
The patient never leaves your page. They are not sent to another site and asked to find their way back. That matters for the rest of the visit: after the check-in you can still collect a copay, show a consent form, or move the patient to the next screen, because you are still in control of the page.
You can try this before installing anything. The clinic demo is a fictional clinic's check-in page; it opens with a demo health app in a second tab, so it works in any browser. The kiosk demo shows a screen with no health app of its own handing the request to a phone. The allergy example fills a form from the response and then asks the patient only for what the record did not contain.
Install
The library is not on npm. Install it from its git repository; you can pin a branch or a specific commit:
npm install github:smart-health-checkin/client
bun add github:smart-health-checkin/client # or bun/pnpm/yarnThe install step compiles the TypeScript, so you get JavaScript and type declarations. If you would rather not install anything, the same code is hosted as an ES module that a page can import directly:
<script type="module">
import { requestCheckin } from "https://smart-health-checkin.org/client/lib/checkin.js";
</script>Your first request
A request is a list of items. Each item describes one thing you want in two ways at once: a title the patient will read, and a description the health app can act on.
import { requestCheckin } from "@smart-health-checkin/client";
const response = await requestCheckin({
purpose: "Before your visit with Dr. Reyes",
items: [
{
id: "allergies",
title: "Allergies and intolerances",
summary: "So we can check them against anything we prescribe.",
content: {
kind: "selection.fhir",
profiles: [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-allergyintolerance",
],
},
accept: ["application/fhir+json"],
},
],
});You wrote purpose and items. The library fills in the rest of the request:
the protocol's type and version fields, a unique id, and the FHIR
versions you accept. The patient sees purpose and each title, so write
them the way you would say them to a person.
content says what data you are asking for. The kind selection.fhir means
"records the app already has that match these FHIR profiles". accept lists
the formats you can handle. Request model covers all the
options.
What you get back
requestCheckin resolves with a response. It has two parts: artifacts, the
data the patient shared, and requestStatus, one entry per item saying what
happened to that item.
for (const artifact of response.artifacts) {
if (artifact.mediaType === "application/fhir+json") {
// artifact.value is a FHIR resource or Bundle — prefill your form with it
}
}
for (const status of response.requestStatus) {
// one entry per item: fulfilled | partial | unavailable | declined | …
}Before the response reaches your code, the library has decrypted it, checked
its signatures, and confirmed that it answers the request you sent — the same
request id, and only formats you said you accept. If any of that fails,
requestCheckin throws instead of returning. Response model
explains artifacts and statuses in detail and shows how to use them to
prefill a form.
Declined, unsupported, and errors
Three things can happen besides success. The patient can decline, either for
the whole request or by closing the health app. The browser may not support
the Digital Credentials API, which is the browser feature this protocol runs
on. Or something can fail along the way. requestCheckin reports all three
by throwing a CheckinFlowError, and e.outcome.status says which it was.
In every sample from here on, names that start with my are yours: the
request you built, the form you already have.
import { requestCheckin, CheckinFlowError } from "@smart-health-checkin/client";
try {
const response = await requestCheckin(myRequest);
prefillMyForm(response);
} catch (e) {
if (e instanceof CheckinFlowError) {
switch (e.outcome.status) {
case "declined": // the patient said no, or closed the wallet
case "unsupported": // this browser has no Digital Credentials API
default: // something broke; e.outcome.error names the stage
}
}
showMyOrdinaryForm();
}Plan for all three paths to end at the form you already have. A check-in saves the patient typing when it works; when it does not, the visit still has to happen, so your ordinary intake form stays the fallback.
If you would rather not use exceptions, runCheckin does the same work and
returns an object with a status field instead of throwing.
Who answers the request
Three kinds of thing can answer a request. The library calls them responders:
- The platform wallet is a health app installed on the patient's device. The browser reaches it through the Digital Credentials API. On a desktop, the browser shows a QR code; the patient scans it with their phone, the app on the phone answers, and the response arrives in the desktop page.
- A web wallet is a health app that is a website. It opens in a new tab, the patient chooses what to share there, and the tab sends the answer back.
- The mock is a stand-in that answers immediately with made-up data. It exists for development and automated tests.
Inside the library these differ in exactly one place. requestCheckin builds
the request, then calls one function to get the health app's sealed answer,
then decrypts and checks that answer. That one function is called
getCredential. By default it is the browser's own navigator.credentials.get,
which reaches the platform wallet. To use a web wallet or the mock, you pass a
different getCredential. The library calls these functions credential
getters, and it provides them; you do not write them.
To let the patient choose, you do three things. You state a policy: which
kinds of responder your page accepts, and which one to present first. The
library turns the policy into a list of responders, each marked with whether
it works in this browser. You render that list — one button per responder is
enough — and when the patient clicks one, you pass that responder's
credential getter to requestCheckin:
import {
resolveResponders, credentialGetterFor, requestCheckin,
} from "@smart-health-checkin/client";
const responders = await resolveResponders({
platform: true, // the wallet installed on the device
webWallets: "/wallets.json", // web wallets you recognize
mock: import.meta.env.DEV, // development only
default: "platform", // the one to lead with
});
for (const responder of responders) {
const button = document.createElement("button");
button.textContent = responder.name;
button.disabled = !responder.available; // e.g. no platform wallet in this browser
button.onclick = async () => {
const response = await requestCheckin(myRequest, {
getCredential: credentialGetterFor(responder),
});
prefillMyForm(response);
};
myMenu.append(button);
}The library does not render anything; the list is data, and the buttons are yours. Wallets and browser support explains the policy options, where the list of web wallets comes from, and how to make the mock return exactly the data a test needs.
Whichever responder answers, the response is encrypted and signed the same way and goes through the same checks. A flow you have tested against the web wallet or the mock is the same flow that will run against a real health app. The clinic demo opens with the demo web wallet selected, so you can watch the whole exchange in any browser.
After the response
Once requestCheckin has returned, the library is finished. It does not know
about your FHIR server, your forms, or your payment step; what you do with the
response is ordinary application code. If you want to write the response to a
FHIR server, the library includes an optional module for that, described in
Writing FHIR. You can equally well use your own client and your
own data model.
From React or Angular
requestCheckin is a plain async function, so a framework binding is small.
It has to do three things: hold the list of responders, make the call, and
track the state of the call. A
React hook and an
Angular service
that do exactly that are live on this site, with their source in
demo/src/frameworks/.
Next: Request model · Response model · Production checklist