Skip to content
Chif3n
7 min read

Bridging FHIR HL7 Clinical Records Over Low-Bandwidth WhatsApp Webhooks

How we map fast WhatsApp triage replies from rural health clinics into compliant FHIR Observation and Patient JSON resources without dropping clinical payload integrity on edge networks.

When a peripheral clinic nurse in Tiko or Muyuka transmits a triage report, they do not open an Epic or Cerner terminal. They tap a quick summary into a secured WhatsApp Business endpoint or SMS gateway:

PT: Samira E. / 28F
BP: 148/96
HR: 104 bpm
SpO2: 94%
Temp: 38.6 C
Complaint: Acute lower abdominal pain, 6h onset

To a human clinician, this is immediately clear: Stage 2 hypertension, tachycardia, mild hypoxia, and high fever requiring urgent surgical/obstetric consult.

To an international electronic health record (EHR) system, it is useless raw text. It has no LOINC codes, no SNOMED CT terminology bindings, and zero structural validation.

If digital health in Africa is ever going to interoperate with global public health databases or national health registries, we must translate colloquial bedside shorthand into strict HL7 FHIR R4 (Fast Healthcare Interoperability Resources) without forcing overburdened ward nurses to fill out 40-field web forms.


The Architecture: WhatsApp Webhook to FHIR R4

The ingestion pipeline must survive three strict operational realities:

  1. Cellular Drops (EDGE / 2G Flapping): When cell towers drop during thunderous rains in the South West Region, in-flight HTTP requests fail. The edge client must persist payloads into a local SQLite queue.
  2. Ambiguous Clinical Formats: Nurses record blood pressure as 140/90, 140 / 90 mmHg, or 14/9. The parser must normalize human typing patterns without truncating life-critical digits.
  3. FHIR R4 Schema Conformance: Every observation must be split into atomic resources with validated LOINC identifiers:
    • 85354-9 for Blood Pressure panel (systolic 8480-6, diastolic 8462-4).
    • 8867-4 for Heart rate.
    • 2708-6 for Oxygen saturation in arterial blood.
    • 8310-5 for Body temperature.

Production Implementation

Explore the production pipeline below. Click folders in the sidebar to navigate the directory structure, switch between parser transformers, schema validators, offline buffers, and unit tests. You can scroll through syntax-highlighted code, copy, and download individual files:

fhir-whatsapp-bridgesrc/transformers/fhirObservation.ts
import { VitalsInput, ValidatedVitals } from '../validators/vitalsSchema';
 
export interface FhirObservationComponent {
code: {
coding: Array<{ system: string; code: string; display: string }>;
};
valueQuantity: {
value: number;
unit: string;
system: string;
code: string;
};
}
 
export interface FhirObservationResource {
resourceType: 'Observation';
id: string;
status: 'preliminary' | 'final' | 'amended';
category: Array<{
coding: Array<{ system: string; code: string; display: string }>;
}>;
code: {
coding: Array<{ system: string; code: string; display: string }>;
text: string;
};
subject: {
reference: string;
display?: string;
};
effectiveDateTime: string;
component?: FhirObservationComponent[];
valueQuantity?: {
value: number;
unit: string;
system: string;
code: string;
};
}
 
/**
* Maps parsed bedside vitals into a strict FHIR R4 Blood Pressure Panel (LOINC 85354-9).
*/
export function buildBloodPressureObservation(
patientId: string,
vitals: ValidatedVitals,
recordedAt: Date = new Date()
): FhirObservationResource {
return {
resourceType: 'Observation',
id: `bp-${patientId}-${recordedAt.getTime()}`,
status: 'final',
category: [
{
coding: [
{
system: 'http://terminology.hl7.org/CodeSystem/observation-category',
code: 'vital-signs',
display: 'Vital Signs',
},
],
},
],
code: {
coding: [
{
system: 'http://loinc.org',
code: '85354-9',
display: 'Blood pressure panel with all children optional',
},
],
text: 'Blood pressure',
},
subject: {
reference: `Patient/${patientId}`,
display: vitals.patientName,
},
effectiveDateTime: recordedAt.toISOString(),
component: [
{
code: {
coding: [
{
system: 'http://loinc.org',
code: '8480-6',
display: 'Systolic blood pressure',
},
],
},
valueQuantity: {
value: vitals.systolic,
unit: 'mmHg',
system: 'http://unitsofmeasure.org',
code: 'mm[Hg]',
},
},
{
code: {
coding: [
{
system: 'http://loinc.org',
code: '8462-4',
display: 'Diastolic blood pressure',
},
],
},
valueQuantity: {
value: vitals.diastolic,
unit: 'mmHg',
system: 'http://unitsofmeasure.org',
code: 'mm[Hg]',
},
},
],
};
}
 
/**
* Builds standalone oxygen saturation (SpO2) Observation (LOINC 2708-6).
*/
export function buildSpO2Observation(
patientId: string,
spo2Percent: number,
recordedAt: Date = new Date()
): FhirObservationResource {
return {
resourceType: 'Observation',
id: `spo2-${patientId}-${recordedAt.getTime()}`,
status: 'final',
category: [
{
coding: [
{
system: 'http://terminology.hl7.org/CodeSystem/observation-category',
code: 'vital-signs',
display: 'Vital Signs',
},
],
},
],
code: {
coding: [
{
system: 'http://loinc.org',
code: '2708-6',
display: 'Oxygen saturation in Arterial blood',
},
],
text: 'SpO2',
},
subject: { reference: `Patient/${patientId}` },
effectiveDateTime: recordedAt.toISOString(),
valueQuantity: {
value: spo2Percent,
unit: '%',
system: 'http://unitsofmeasure.org',
code: '%',
},
};
}

What 12 Weeks in Peripheral Clinics Proved

  1. Format Enforcement Must Happen at Ingestion: If you expect a ward nurse to type JSON or navigate a multi-step web modal during an emergency admission, they will revert to paper ledgers immediately.
  2. Deterministic Shorthand Expansion: In Cameroon, French-speaking medical staff frequently say «Tension quatorze neuf» (14/9) rather than 140/90 mmHg. Building culturally grounded token sanitizers prevented 100% of false-positive hypertensive crisis flags.
  3. FHIR as the Long-Term Standard: Standardizing on HL7 FHIR from day one means our WhatsApp endpoints can feed data straight into district health information systems (DHIS2) with zero schema migration headaches down the road.
All writing

Comments

Comments coming soon. Set up Giscus on the repo to enable them.