Remote patient monitoring with FHIR
From home blood pressure readings to alerts: device Observations, signed webhooks, an idempotent rule, and what stays outside the data layer. Tested code.
Remote patient monitoring (RPM) collects measurements a patient takes at home — blood pressure, weight, glucose, oxygen saturation — and lets a care team see them and act on them between visits. The data part has four steps: get each reading into the patient's record as a coded, device-attributed Observation; react when a new reading arrives; decide with a clear rule whether it needs attention; and keep a record of what the team did. This page builds that pipeline on HL7 FHIR R4 with Anpheros webhooks and the TypeScript SDK, with code that was run against the platform, and lists what stays outside the data layer.
The pipeline
cuff, scale, glucose meter, pulse oximeter
│ (Bluetooth / vendor app / Apple Health / Health Connect)
▼
patient's phone app ──► your backend ──► FHIR Observation (author: device) in the patient's record
│ webhook: resource.created (signed)
▼
your monitoring service: rule → alert → care team
│
note, call, CarePlan change, next appointment
Three choices decide whether this works in practice:
- One reading, one Observation. Each cuff reading is an Observation with LOINC 85354-9 and two components (8480-6 systolic, 8462-4 diastolic), the measurement time, and
author_type: device. Unlike daily wearable summaries, RPM readings are individual measurements a clinician may look at one by one. - React to events, not to a timer. A webhook per new Observation lets the rule run seconds after the reading arrives.
- Rules over several readings. A single high reading is often noise; most protocols look at a series. The rule below asks for three consecutive readings at or above 140/90. That threshold is an example, not clinical advice: the care team sets the rule for each patient.
Writing readings from the device
The phone app (or the device vendor's cloud, through your backend) writes each reading:
await anpheros.observations.create(patientId, {
code: '85354-9', category: 'vital-signs', effective_at: '2026-10-13T07:15:00-04:00', author_type: 'device',
components: [{ code: '8480-6', value: 152, unit: 'mm[Hg]' }, { code: '8462-4', value: 95, unit: 'mm[Hg]' }],
}, { source_system: 'bp-cuff-app' });
If readings come through Apple Health or Health Connect rather than from the device directly, read them on the phone as described in Wearable data in FHIR, but keep individual blood pressure readings as individual Observations instead of daily averages.
Reacting to new readings
Register a webhook endpoint once (POST /v1/webhooks with events: ["resource.created"]); the response contains the signing secret. Then run a small service that verifies each delivery and evaluates the rule:
import { createServer } from 'node:http';
import { Anpheros, apiKey, verifyWebhookSignature } from '@anpheros/sdk';
const anpheros = new Anpheros({ auth: apiKey(process.env.ANPHEROS_KEY!) });
const SECRET = process.env.WEBHOOK_SECRET!;
const seen = new Set<string>(); // deliveries are at-least-once: de-duplicate by event id
const alerted = new Set<string>(); // one alert per newest reading, however many events arrive
// Rule: the three most recent home readings all at or above 140/90 → tell the care team once
async function checkBloodPressure(patientId: string, observationId: string) {
const obs = await anpheros.observations.get(observationId);
if (obs.code !== '85354-9') return; // only blood pressure readings trigger the rule
const { data } = await anpheros.observations.list(patientId, { code: '85354-9', limit: 3 }); // newest first
const value = (o: typeof obs, code: string) => o.components.find(c => c.code === code)?.value ?? 0;
const allHigh = data.length === 3 && data.every(o => value(o, '8480-6') >= 140 || value(o, '8462-4') >= 90);
if (!allHigh || alerted.has(data[0].id)) return;
alerted.add(data[0].id);
console.log(`ALERT patient ${patientId}: last 3 readings ≥ 140/90 →`,
data.map(o => `${value(o, '8480-6')}/${value(o, '8462-4')}`).join(', ')); // in production: notify the care team
}
createServer(async (req, res) => {
const chunks: Buffer[] = [];
for await (const c of req) chunks.push(c as Buffer);
const body = Buffer.concat(chunks).toString('utf8');
const ok = await verifyWebhookSignature({ secret: SECRET, header: req.headers['anpheros-signature'] as string, body });
if (!ok) { res.writeHead(400).end(); return; } // not from Anpheros, or too old
res.writeHead(204).end(); // answer fast; do the work after
const event = JSON.parse(body);
if (seen.has(event.id)) return;
seen.add(event.id);
const d = event.data;
if (event.type === 'resource.created' && d.resource_type === 'Observation') await checkBloodPressure(d.patient, d.id);
}).listen(8799);
How we tested it: four morning readings (136/86, 144/92, 149/94, 152/95) were written to a synthetic patient on a local instance of the platform, and the platform produced one signed resource.created event per reading. Those events, signed with the platform's own signing code, were sent to the service above together with two duplicates and one request signed with a wrong secret. The result: the wrong signature got 400, the duplicates were ignored, and the service raised exactly one alert — 152/95, 149/94, 144/92 — not one per event.
In production, keep seen and alerted in a database rather than in memory, so a restart does not repeat alerts.
Receiving webhooks while developing. Anpheros accepts and delivers only to public HTTPS endpoints: a localhost or private address is refused when you register it (400, field url), in the sandbox too, and every delivery checks the address again. To receive events on your laptop, expose the service through a public HTTPS tunnel and register the tunnel's address. See Webhooks.
What the record should show afterwards
The alert itself lives in your monitoring service, but the record should show what came of it, so the next clinician and the patient see the whole story:
- the readings, each marked as device data with its source system (provenance);
- a
CarePlanwith the monitoring plan (what is measured, how often, the goal); - an
Encounter(classVRfor a remote contact) when the team calls the patient, and any medication change as aMedicationStatement.
Anpheros stores these resources; it does not have an alerting or task resource of its own (Flag, Task and Communication are not among its resource types), so alert queues and on-call routing belong to your service.
Billing, regulation and safety
RPM programmes often depend on reimbursement rules — in the United States, Medicare pays for remote physiologic monitoring under specific CPT codes with minimum numbers of days with data and time spent by the care team. Those rules change and are not something a data platform can certify; check the current CMS rules and keep the evidence (the readings and the team's time) in your own system. Software that interprets readings to drive diagnosis or treatment may be regulated as a medical device. Keep thresholds in the hands of clinicians and log every alert and response.
Where Anpheros fits
Anpheros provides the record (FHIR R4 Observations with provenance, CarePlan, Encounter), signed webhooks with retries, consent if another organisation's app needs to read the readings, and an access log the patient can see. It does not connect to device vendors, decide what is abnormal, send alerts or handle billing.
Frequently asked questions
Which FHIR resource stores a home blood pressure reading?
An Observation with LOINC 85354-9 (blood pressure panel) and two components, 8480-6 (systolic) and 8462-4 (diastolic), each a valueQuantity in mm[Hg], with the measurement time and the device as the author.
Should RPM data be daily averages or individual readings?
Individual readings for clinical measurements such as blood pressure, weight or glucose, because clinicians review and count them. Daily summaries suit high-frequency wearable data such as heart rate or steps.
How do I trigger an alert when a new reading arrives?
Subscribe to resource.created webhooks, verify the signature, fetch the new Observation and evaluate your rule over the recent series. Deliveries can repeat, so de-duplicate by event id and make the alert idempotent.
Can Anpheros send alerts to clinicians?
No. It notifies your service through webhooks; who is alerted, how and when is decided in your system.