Skip to content

House of Wellness — Supplier Handover Guide

Last updated: 10 April 2026

This document is intended for suppliers and technical partners who need to understand how the HOW Integration Platform interacts with their systems, where data resides, and how it flows through the platform.


1. Platform Summary

The HOW Integration Platform is a private web application used by House of Wellness UK staff to manage blood-test workflows. It integrates three external supplier systems:

Supplier Role Integration Type
Randox Health Laboratory — blood test orders, results, PDFs REST API (APIM gateway)
OptimalDX Report interpretation — functional health reports REST API (apiKey auth)
GoHighLevel (LeadConnector) CRM — patient records, email/SMS communications REST API (OAuth2)

Production URL: https://results.houseofwellnessuk.com (private, Entra ID authenticated)


2. System Architecture Overview

graph TB
    subgraph "House of Wellness"
        PORTAL["Staff Portal<br/>(Azure Static Web App)"]
        API["Integration API<br/>(Azure Functions, .NET 10)"]
        STORAGE["Azure Storage<br/>(Table + Blob)"]
        KV["Azure Key Vault<br/>(Credentials)"]
    end

    subgraph "Randox Health"
        RAPI["Randox API<br/>(APIM Gateway)"]
    end

    subgraph "OptimalDX"
        OAPI["OptimalDX API<br/>(REST v1)"]
    end

    subgraph "GoHighLevel"
        GAPI["LeadConnector API<br/>(REST)"]
    end

    PORTAL -->|"HTTPS /api/*"| API
    API --> STORAGE
    API --> KV
    API <-->|"Orders & Results"| RAPI
    API <-->|"Patients & Reports"| OAPI
    API <-->|"Contacts & Comms"| GAPI

3. Data Residency

All data at rest is stored in Azure UK South (West Europe for the static web app front-end only):

Data Type Storage Location Region Encryption
Case workflow state Azure Table Storage UK South AES-256 at rest
Raw Randox results (JSON) Azure Blob Storage UK South AES-256 at rest
HL7 messages to OptimalDX Azure Blob Storage UK South AES-256 at rest
Report PDFs (ODX + Randox) Azure Blob Storage UK South AES-256 at rest
Test Order Form PDFs Azure Blob Storage UK South AES-256 at rest (temporary, deleted on completion)
Clinical notes Azure Table Storage UK South AES-256 at rest
API credentials & secrets Azure Key Vault UK South HSM-backed
Portal static assets Azure Static Web App West Europe AES-256 at rest

Blob Storage Layout

artifacts/
├── randox/
│   ├── raw/{caseId}.json          ← Raw Randox results
│   ├── pdf/{caseId}/{file}.pdf    ← Randox report PDFs
│   └── tof/{caseId}.pdf           ← Test Order Forms (temporary)
└── odx/
    ├── hl7/{caseId}.hl7           ← HL7 messages sent to OptimalDX
    └── pdf/{caseId}/{file}.pdf    ← OptimalDX report PDFs

4. Data Flow — Randox

What we send TO Randox

Data When Purpose
Patient demographics (name, DOB, biological sex, ethnicity) Order creation Required for blood test order
Panel and test selections Order creation Which tests to run
Sample collection details (date, lab, tubes) Order submission Lab processing metadata
External order number (FML001-XXXXXXXX) Order creation Our tracking reference

What we receive FROM Randox

Data When Purpose
Randox order ID + order number Order creation response Stored for tracking
Order status (submitted/pending/completed) Hourly polling Workflow progression
Results detail (analytes, values, units, reference ranges) On completion Stored as raw JSON, forwarded to OptimalDX
Report PDFs (microbiome, NGS, health check panels) On completion Stored and emailed to patient

Data Flow Diagram

sequenceDiagram
    participant HOW as HOW Platform
    participant R as Randox API

    Note over HOW,R: Order Lifecycle
    HOW->>R: POST /auth/token (credentials)
    R-->>HOW: Bearer token

    HOW->>R: POST CreatePendingOrder<br/>(patient, panels, tests, externalNumber)
    R-->>HOW: { orderId, orderNumber }

    HOW->>R: POST UpdateOrder<br/>(sample collection details)
    R-->>HOW: OK

    loop Hourly polling (status 2/3)
        HOW->>R: POST GetOrderStatus<br/>(orderId/orderNumber)
        R-->>HOW: { statusId, statusDescription }
    end

    Note over HOW,R: On Completion (status ≥ 4)
    HOW->>R: POST GetOrderResultDetail<br/>(orderId/orderNumber)
    R-->>HOW: { reportResults[ analyte, result, units, refRange ] }

    opt PDF-only panels
        HOW->>R: POST GetOrderResultReports
        R-->>HOW: { base64 PDF }
    end

Randox Data Retained

Item Retention Location
Raw results JSON Indefinite Blob: randox/raw/{caseId}.json
Report PDFs Indefinite Blob: randox/pdf/{caseId}/*.pdf
Order references (ID, number, external number) Indefinite Table: Cases entity fields
Test Order Form Deleted on completion Blob: randox/tof/{caseId}.pdf

5. Data Flow — OptimalDX

What we send TO OptimalDX

Data When Purpose
Patient identity (name, DOB, gender, email) Patient sync Create/match patient in ODX
External patient ID (= CRM contact ID) Patient sync Cross-system linking
Blood test results (HL7 ORU^R01 format) After Randox results pulled Results interpretation
Report generation request (practice, patient, test IDs) After results submitted Generate Functional Health Report PDF

What we receive FROM OptimalDX

Data When Purpose
Patient ID Patient sync response Stored for subsequent calls
Patient test ID Results submission response Used for report generation
Functional Health Report PDF Report generation response Stored and emailed to patient
Available report types On demand UI report selection

Data Flow Diagram

sequenceDiagram
    participant HOW as HOW Platform
    participant ODX as OptimalDX API

    Note over HOW,ODX: Patient & Results
    HOW->>ODX: POST practice/{id}/patient<br/>(name, DOB, gender, email)
    ODX-->>HOW: { patientId }

    HOW->>ODX: POST patient/{id}/partner/{externalId}<br/>(link CRM contact ID)
    ODX-->>HOW: OK

    HOW->>ODX: POST patient/{id}/test<br/>(HL7 ORU^R01 message, labProfileId)
    ODX-->>HOW: { patientTestId }

    Note over HOW,ODX: Report Generation
    HOW->>ODX: GET reports/{group}/practice/{id}<br/>(list available reports)
    ODX-->>HOW: [ { reportId, name, ... } ]

    HOW->>ODX: POST reports/FunctionalHealthReport<br/>(practiceId, patientId, patientTestId, reportIds[])
    ODX-->>HOW: PDF bytes

HL7 Message Format

Results are submitted as HL7 v2.3 ORU^R01 messages:

MSH|^~\&|HOW|HOW|OptimalDX|OptimalDX|...||ORU^R01|...|P|2.3
PID|||{patientId}^^^MR||...
OBR|1|{orderNumber}||RANDOX^Blood Test Results^L|||...
OBX|1|NM|{code}^{name}^99RDX||{value}|{units}|{refLow}-{refHigh}|{flag}|||F
OBX|2|NM|...

Each analyte from Randox is mapped to an OBX segment with code system 99RDX.

OptimalDX Data Retained

Item Retention Location
HL7 messages Indefinite Blob: odx/hl7/{caseId}.hl7
Report PDFs Indefinite Blob: odx/pdf/{caseId}/*.pdf
Patient/test IDs Indefinite Table: Cases entity fields

6. Data Flow — GoHighLevel (CRM)

What we read FROM CRM

Data When Purpose
Contact details (name, email, phone, DOB, biological sex) Case creation, order creation Patient identity
Custom field values (DOB, biological sex, athlete) Order creation Randox/ODX requirements

What we write TO CRM

Data When Purpose
New contacts Patient creation from portal CRM is system of record
Updated contact fields Portal edits (DOB, sex, etc.) Keep CRM current
PDF attachments After report generation Conversation attachments
Emails (with PDF) After report generation Patient notification
SMS messages After report generation Patient notification

CRM Data Flow Diagram

sequenceDiagram
    participant Staff as Portal Staff
    participant HOW as HOW Platform
    participant CRM as GoHighLevel

    Staff->>HOW: Search for patient
    HOW->>CRM: GET contacts/?query=...
    CRM-->>HOW: Contact list
    HOW-->>Staff: Display results

    Staff->>HOW: Create/update patient
    HOW->>CRM: POST/PUT contacts/
    CRM-->>HOW: { contactId }

    Note over HOW,CRM: After Report Generated
    HOW->>CRM: POST conversations/messages/upload<br/>(PDF file)
    CRM-->>HOW: { attachmentUrl }

    HOW->>CRM: POST conversations/messages<br/>(type=Email, attachments, subject, html)
    CRM-->>HOW: { messageId }

    HOW->>CRM: POST conversations/messages<br/>(type=SMS, message)
    CRM-->>HOW: { messageId }

CRM Data Retained Locally

Item Retention Location
CRM contact ID (reference only) Indefinite Table: Cases.CrmContactId
CRM location ID (reference only) Indefinite Table: Cases.CrmLocationId
Patient name (cached display) Indefinite Table: Cases.PatientName
Clinical notes Indefinite Table: ContactNotes

Note: Patient PII (full demographics) is NOT permanently stored in our system. The CRM contact ID is stored as a reference. Full patient details are fetched from CRM on demand.


7. Security Summary

Control Implementation
Transport HTTPS only (TLS 1.2+) on all endpoints
User authentication Entra ID (Azure AD) via SWA built-in auth
Role-based access How_Admin, How_Staff, How_ReadOnly roles enforced server-side
Credential storage Azure Key Vault (HSM-backed), accessed via Managed Identity
No connection strings All Azure resources accessed via DefaultAzureCredential
Secrets never logged Diagnostic endpoints return metadata only, never secret values
Data at rest AES-256 encryption (Azure Storage + Key Vault)
API resilience All HTTP clients use retry + circuit breaker + timeout (Microsoft.Extensions.Http.Resilience)
CI/CD GitHub Actions with OIDC federation (no stored credentials)

8. Environment Summary

Resource Production Dev (stopped)
Subscription prod-spoke-uks dev-spoke-uks
Resource Group how-prod-integration-rg how-poc-integration-rg
Function App how-prod-api-func how-poc-api-func ⏸️
Static Web App how-prod-portal how-poc-portal
Docs Site how-prod-docs
Storage Account howprodintegration howpocintegrationrg9f7a
Key Vault how-prod-kv how-poc-kv
Custom Domain results.houseofwellnessuk.com
Docs Domain docs.houseofwellnessuk.com
Framework .NET 10 (LTS)
Region (compute) UK South UK South
Region (SWA) West Europe West Europe

9. Contact

Role Contact
Technical Lead Devtech Cloud (josh@devtechcloud.com)
Business Owner House of Wellness UK