Dentolize · DHS (NPHIES) Integration Walkthrough
On this pageBusiness viewTechnical viewA note on honesty

DHS (NPHIES) Integration — Overview

Pull request: #119 · maysara/DHS-integrationmain Status: Unreleased. Gated behind the FEATURE_DHS_INTEGRATION flag, which is seeded with a beta-only rule. Nothing in here is live for general customers.


Business view

Clinics in Saudi Arabia and the wider Gulf cannot simply invoice an insurer and hope for the best. Before treating an insured patient, the clinic has to ask the payer three questions, in order:

  1. Who actually insures this person? A patient walks in with a national ID or an

Iqama. The clinic needs to know which insurer, which policy and which class of cover they belong to — information the patient often does not know or gets wrong.

  1. Is their cover live today? Policies lapse, employers switch insurers, and

dependants get removed. An eligibility check answers "is this person covered right now, and for how much?"

  1. Will you pay for this specific treatment? For anything beyond routine work the

payer wants to approve the treatment plan up front. That is a pre-authorization, or pre-auth: the clinic sends the proposed procedures, diagnoses and supporting files, and the payer replies approved / partially approved / rejected — sometimes minutes later, sometimes days.

Today clinics do all three on the payer's own web portal, retyping patient details and procedure codes by hand, then retyping the answer back into Dentolize. It is slow, it is error-prone, and the clinic frequently discovers a rejection only after the treatment has been done and the invoice raised.

This feature moves all three steps into Dentolize. DHS (the Motalabate Health platform, which fronts the Saudi NPHIES national exchange) becomes a service the app talks to on the clinic's behalf. The receptionist checks insurance from the patient screen; the dentist submits a pre-auth straight from the treatment chart; and the app polls the payer in the background so the approval number and the approved amounts land on the operations automatically.

The other half of the feature is protection. Once a treatment is sitting with a payer awaiting approval, Dentolize stops staff from quietly changing the price, the tooth or the diagnosis underneath it, and stops the treatment being invoiced before the payer has answered. That keeps what the clinic billed and what the payer approved in sync.

What a clinic actually gets

CapabilityWhere it lives in the app
Store and test the clinic's DHS credentialsSettings → Integrations → DHS Integration
Map each branch to its NPHIES provider codeSame wizard, step 2
Discover a patient's insurer / policy / class from their IDPatient profile → Check Insurance
Create the matching insurer records locally, in one flowSame wizard
Confirm cover is activePatient profile → Check Eligibility
Submit a pre-auth for selected treatmentsPatient chart → GET Approval
Watch approvals and their statusPatient chart → Approvals tab
Chase, cancel, or hand-reconcile an approvalStatus popover on the Approvals tab
Automatic status polling and amount reconciliationBackground job, every minute

Technical view

The integration spans four packages. There is no mobile UI — clinic-mobile only carries the shared GraphQL documents, the permission map, translations, and guards that disable existing actions.

PackageRole
packages/server12 mutations, 7 queries, a cron job, encryption + validation utilities, permission rules
packages/clinic-web-canaryAll six DHS UI feature modules (React + TypeScript)
packages/clinic-webHost app — mounts the canary modules, adds the Approvals tab, status popover, routes, permission-group tab
packages/prismaThree new models, DHS fields on six existing models, eight migrations

The three outbound APIs

Configured by environment variable, resolved at module load, and fail-fast — a missing variable throws before the process starts (packages/server/src/utils/dhsConfig.js:1-11).

VariableUsed for
DHS_AUTH_URLPOST /api/Login — exchange client secret for a bearer token
DHS_ELIGIBILITY_URLPOST /api/v1/Eligibility/CheckInsurance, POST /api/v2/Eligibility/Checkeligibility
DHS_PREAUTH_URLPOST /api/v1/Preauth/{SubmitApprovalRequest,GetApprovalResponse,CancelApproval}

ENCRYPTION_MASTER_KEY (64 hex chars) is also required, and is validated at import time (packages/server/src/utils/encryption.js:6-14).

The data flow, end to end

Clinic saves client secret
  └─ validated against /api/Login, then AES-256-GCM encrypted into DHSIntegration

Receptionist: Check Insurance (national ID)
  └─ checkInsuranceEligibility  → DHS /api/v1/Eligibility/CheckInsurance
     └─ resolveInsuranceCoverages → "do we already have this insurer/policy/class?"
        └─ wizard creates the missing InsuranceCompany / InsurancePolicy / PolicyClass

Receptionist: Check Eligibility
  └─ dhsCheckEligibility → DHS /api/v2/Eligibility/Checkeligibility
     └─ writes DHSEligibilityCheck, points Patient.currentEligibilityId at it

Dentist: select operations → GET Approval
  └─ dhsApprovalSubmission → DHS /api/v1/Preauth/SubmitApprovalRequest
     └─ writes DHSApproval (status PENDING), links Operations, stamps Operation.preAuth

Every minute: dhsApprovalsCron
  └─ DHS /api/v1/Preauth/GetApprovalResponse for up to 100 PENDING approvals
     └─ maps payer status → DHSApprovalStatus
     └─ writes Operation.approved and Operation.insuranceValue

Every write is scoped by companyId, and every DHS GraphQL field is gated by both a permission and the feature flag — see Permissions, Tenancy & Rollout.

Where to start reading

Eligibility Checks


A note on honesty

These pages describe what the code on this branch does, verified by reading /work/repo and by driving the branch sandbox. Where the PR description and the code disagree, the code wins and the discrepancy is called out. The most notable one:

The PR description lists permissions MANAGE_DHS_INTEGRATION, MANAGE_DHS_APPROVALS and CHECK_INSURANCE. Those names exist only as orphaned values in the Postgres enum from migration 20260615152553_dhs_permissions. The permissions the code actually enforces are nine finer-grained ones — a repo-wide search for MANAGE_DHS returns zero hits. See Permissions, Tenancy & Rollout.