03 · Consent
You send who, where and why. The rail picks the factor — fingerprint, face, one-time code, guardian or eKYC — runs it, and returns a proof. You never choose the factor, integrate a scanner, or branch per payer. Render what you are given, and keep the proof.
Everything below elaborates this sentence.
What this stop does
Consent answers two things at once: is this really the person, and may this data be seen or this
action taken. One call does both. Which factor is legal — for this member, at this facility, on
this payer, for this purpose — is decided server-side and versioned there, so your code carries no
if minor, no if whitelisted, and no biometric vendor's name.
It stands on its own: you can ask for consent to read a record or check a cover without any claim or visit existing.
What you send
Ask for an authentication. A thin body in, a decision and a next step out.
authentication_id + collection.urlThe subject is the patient in the path; the facility comes from the X-Facility-Id /
X-Facility-Id-Type headers. Neither is read from the body.
| Call | What it does | What you keep |
|---|---|---|
network | Which payer network this is for. It selects the rules that apply and the conduit the factor runs on. | — |
purpose | Why you are asking. It picks the rules and becomes the scope the proof may be spent on. | — |
agent_id | Required. The national ID of the operator asking — the audit note for who requested it, not routing. | — |
callback_url | Required. Where the outcome is delivered; a registered endpoint is used only when this is absent. | — |
selection_ref | Optional. The cover in scope, from covers/select at 02. | — |
Do not send hardware context — no workstation, agent or device identifiers. The collection UI captures those from the machine that does the capture.
What you get back — two shapes
Switch on next_action. Assuming there is always a URL hangs every code path; assuming there
never is strands every biometric capture.
| Call | What it does | What you keep |
|---|---|---|
next_action: collect | A strong factor was chosen. You get status pending and a collection.url — embed it, and everything the member sees and does happens inside it: factor choice, capture, retries, fallbacks. | collection.url |
next_action: await_callback | A one-time code was dispatched. Status pending, nothing to render — show a 'code sent' state and wait. | authentication_id |
The outcome
Read the record for the result — never treat the capture window closing as success. A push arrives
on your callback_url; poll this only as the safety net.
Read the authentication back.
status + expires_at| Call | What it does | What you keep |
|---|---|---|
pending | Dispatched or awaiting capture. Not a failure, not something to retry. | — |
verified | The factor succeeded. A freshness window starts at verified_at. | expires_at |
failed | Terminal. Raise a new authentication rather than retrying this one. | the reason |
Consumption and expiry are not statuses: whether a proof can be spent is decided when you spend it. Attempt the redemption and handle the refusal rather than branching on a status.
Purpose is the scope
Purpose picks which rules run and bounds what the proof may later be spent on.
| Call | What it does | What you keep |
|---|---|---|
eligibility_check | Confirm cover, run an eligibility search. | — |
cover_view | View benefit or cover detail. | — |
visit_start | Open a visit, begin care. | — |
visit_end | Close a visit or discharge. Always needs its own authentication. | — |
pii_access | Read a patient summary or clinical record. | — |
A proof is a resource, not an event
A verified authentication is persisted, has a freshness window, and — depending on the factor —
can be spent more than once. Keep the authentication_id, not just the fact that it worked.
| Call | What it does | What you keep |
|---|---|---|
Strong factors | A fingerprint or eKYC verification backs several intents while fresh — an eligibility search and, later, opening the visit. Do not re-authenticate defensively. | authentication_id |
Weak factors | A one-time code is spent once; a second redemption is refused. | — |
Standalone purposes | Some are never satisfied by an existing proof. Closing a visit is one — always take a fresh authentication. | a fresh one |
Sensitive data is rendered, not fetched
For clinical reads the flow inverts: never fetch the record into your client and then ask for consent. Consent produces a single-use view token, you embed a viewer, and the data streams into that frame. Your application never holds the payload, and the token is burned on read — to show the same record twice, ask twice (cheap when a fresh proof exists).
Inside the hosted surface
You do not call these in a normal integration — the surface renders inside the iframe and the browser posts to them. They are here because test harnesses drive the surface headlessly, and the failure below looks like a bug in your code when it is not.
Send the passcode for the launched authentication. Form-encoded in, HTML fragment out.
the re-rendered verify slotCheck the code the member read back. A refusal re-renders the slot with the executor's own message.
the receipt fragmentBoth take authentication_id, contact_id, unique_patient_id, purpose, selection_ref,
callback_url (and verify adds otp). The surface re-runs the consent decision on every post,
so the whole context must travel with each one. The session and tenant come from the hie_session
cookie and an X-Csrf-Token header (echoing hie_csrf), both scoped to the UI mount path.
Age and guardian
| Call | What it does | What you keep |
|---|---|---|
Under 7 | No fingerprint. The guardian consents; the code goes to the guardian. | guardian identity |
7 to 17 | Prints enrolled at the point of care, then verified; else the guardian's code. | guardian identity |
18 and over | The member verifies; the code goes to the member. | member contact |
Guardian consent is a modifier on the underlying factor and inherits its strength — a guardian's fingerprint is strong, a guardian's code is weak. The record carries who consented and on whose behalf.
Break-glass
When someone arrives unable to consent and care cannot wait, there is a traced exception: the practitioner authenticates to their own identity (only ones the health worker registry knows may do it) and gives a reason from a constrained list plus a narrative. Skip it and the rest of the journey does not work — the ambulance leg cannot be claimed and the patient cannot be handed over. Build the path, and make it look exceptional in your interface.
Which factors a facility offers
Resolved for you, but the resolution explains most "the scanner is broken" tickets.
| Call | What it does | What you keep |
|---|---|---|
Facility enforces biometrics | The normal case — fingerprint first, code where the member qualifies. | — |
Facility de-enforced | The one-time code becomes available to EVERY patient there, whitelisted or not. | — |
Member individually whitelisted | The code path for this person, at any facility. | — |
Biometric no-match is normal (~3 adults in 10); the embedded UI falls back to the code path and raises a whitelist request where needed. Do not build a retry loop around the reader.
What breaks here
| Where it surfaces | What the desk sees | What actually went wrong |
|---|---|---|
| One-time code path | "The integration hangs waiting for a URL that never arrives" | A weak factor returns next_action await_callback with nothing to render. Switch on next_action. |
| Embedded frame | "The frame loads with a doubled host and never renders" | collection.url is absolute. Use it as the iframe src verbatim — do not prefix a base URL. |
| OTP surface (harness) | "'unique_patient_id is required' but we got a 200" | The hosted OTP endpoints answer 200 on failure with the message in the fragment. Read the fragment, not the status. |
| Any sensitive view | "The record was on the client before consent was granted" | Data was fetched, then permission asked. Ask first, render inside the viewer, never hold the payload. |
| Every capture | "We ask for a fingerprint twice in one visit" | A strong proof was treated as an event. A fresh one backs several purposes — keep authentication_id and spend it. |
| Discharge | "The proof from admission is refused at discharge" | Closing a visit is standalone. No proof taken for another purpose satisfies it. |
| Outcome | "We showed consent as granted and the claim was rejected for no authorisation" | The frame closing was treated as the answer. Only the record read or the signed callback establishes consent. |
Next
Next: 04 · Visit
A complete brief for this step — the calls to make, what to persist, and every failure path to handle. Nothing on this page is assumed.

