Custom Software With HL7 and FHIR Integration: Feasibility, Cost and Real Risk
Adding HL7 and FHIR integration to an existing product typically runs $15,000 to $60,000 for a focused, single-connection build. As part of a first release it lands at $50,000 to $130,000 over 10 to 16 weeks, and a full multi-tenant clinical platform runs $150,000 to $350,000 over 6 to 12 months. Those are Digital Heroes delivery bands. The number moves on how many distinct health systems you connect to, whether you need write access, and whether you need real time data or can live with a nightly batch.
What this actually is once real patients touch it
HL7 and FHIR integration sells as plumbing. Read a chart, write a note, done. In a sandbox that is true. The sandbox has ten patients, one identifier each, and nobody in it ever changes their mind.
The version that ships: you build ADT ingestion for one hospital, keyed on the medical record number in PID-3. It runs clean for six weeks. Then a registration clerk notices the same patient exists twice, once created at the ED desk and once by the referral team, and merges the two charts. The EHR emits an ADT^A40 with an MRG segment carrying the retired identifier. Your parser has a switch covering A01, A03, A04 and A08. A40 falls through to a warning log nobody reads.
From that moment your database believes in two people. New labs land under the surviving MRN, everything historical sits under the dead one, and neither view is complete. The clinician opening your app sees a medication list missing the anticoagulant. That is the 3am page, and it is not a hotfix, because you now have to reconcile every foreign key you ever wrote against an identifier the hospital has retired and will never send you again.
HL7 v2 is a transport, not an agreement
v2 messages arrive over MLLP, a TCP framing where each message is wrapped in a 0x0B start byte and a 0x1C 0x0D trailer. Not HTTPS. Not the public internet. It comes down a site to site VPN into a listener you operate.
The traps, in the order they bite:
Acknowledgements. MSH-15 is accept acknowledgement and MSH-16 is application acknowledgement, and they are different promises. If you send an AA on receipt and then your worker crashes before the write lands, the hospital believes it delivered and will never resend. Only ACK after durable persistence of the raw bytes.
Duplicates. Interface engines replay on restart. MSH-10, the message control ID, scoped by the sending facility in MSH-4, is your idempotency key. Assume every message can arrive twice.
Ordering. A single MLLP connection is ordered. The moment you fan out to parallel workers you can apply an A08 update before the A01 that created the encounter. Sequence on MSH-7 and guard every write with the message timestamp.
Time. v2 timestamps routinely arrive as YYYYMMDDHHMMSS with no UTC offset. The hospital means local time. On the fall daylight saving boundary you get two 01:30s. Store the site timezone per interface and never infer it from the payload.
The dialect problem. Two hospitals both running 2.5.1 will use different fields for the same fact, and both will bolt on Z-segments they invented. The old line holds: if you have seen one HL7 interface, you have seen one HL7 interface. Budget per interface, not per standard.
Escaping. Fields carry escape sequences like \F\, \S\, \T\, \R\ and \E\ for the delimiter characters, with the encoding declared in MSH-18. A naive split on the pipe character breaks the first time a free text note contains an ampersand.
Results. Long reports split across repeating OBX segments and reassemble by the OBX-4 sub-ID. OBX-11 carries the status: P preliminary, F final, C corrected, X cannot obtain. Ingest only F and you silently drop corrections. A potassium corrected from 3.1 to 6.8 is a safety event, not a data quality ticket.
A competent build terminates MLLP in an interface engine (NextGen Connect, formerly Mirth, plus Rhapsody, Cloverleaf, Iguana and Corepoint are the ones you will meet), lands raw bytes verbatim in a durable queue, and treats parsing as a separate replayable stage. You will re-parse. Keep the originals forever.
FHIR R4 gives you a shape, not a change feed
Build to R4 (4.0.1). R5 has been published since 2023 and effectively no major EHR serves it in production. In the US the profile that matters is the US Core Implementation Guide, pinned to a USCDI version. Ask which US Core version a vendor supports and expect a specific number, because 3.1.1 and 7.0.0 are not the same contract.
The thing nobody tells buyers: R4 has no usable change notification. Subscriptions are barely deployed, and topic based subscriptions live in R5 and the Subscriptions Backport IG. So you either poll _lastUpdated, which some servers set on internal churn and others barely set at all, or you take an HL7 v2 ADT and ORU feed for anything real time and use FHIR for on demand depth. Most production systems we ship run both. People who budgeted for "just FHIR" find this out in week three.
Reading a full chart is dozens of paginated calls followed by the next link, with per-tenant throttling. Bulk data uses the async pattern: kick off $export, get a 202 with a Content-Location, poll while honoring Retry-After, then download ndjson files that can reach tens of gigabytes. The engineering is in the resume and the checkpoint, not the request.
Auth is SMART on FHIR over OAuth 2.0. Scope families matter: patient/*.read, user/Observation.read, system/*.read. Backend services use client_credentials with a JWT signed by your private key against a JWKS URL you host and rotate. A misconfigured JWKS is the single most common launch day failure.
Write access is a separate negotiation, not a separate endpoint. Read scopes are routine. Writing Observation, Condition or DocumentReference is per vendor, per customer, and often per resource. The sandbox will happily accept POSTs that production will never approve.
Identity and terminology are the actual project
The US has no national patient identifier, so matching is probabilistic and every health system runs its own master patient index with a real duplicate rate. Model identity as a graph, not a column: store every identifier with its system, keep a survivorship table, handle A40 merges as first class events, and make them reversible, because clerks unmerge too.
Then the codes. The lab sends its local code GLU and you need LOINC 2345-7. Medications need RxNorm. Problems need SNOMED CT. Procedures and billing need CPT, which is AMA licensed and costs money to redistribute, while ICD-10-CM is free. UMLS access is free but requires a license account. Expect several hundred to a few thousand local codes per site, and expect a clinician to sign off on the mapping. That table is the deliverable, and it is the line item nobody quotes.
Units follow UCUM. A glucose of 5.5 mmol/L and 99 mg/dL are the same person. Store value, unit and code system together and convert at display, never at ingest, or you will lose the original forever.
Getting to production is procurement, not code
Epic gives you a free sandbox and a non-production client ID in minutes. Production requires the health system itself to enable your app in their environment, with a different client ID, and that paperwork runs weeks to a quarter. Oracle Health, formerly Cerner, works the same way through its code console. MLLP needs a site to site VPN, a static egress IP, allowlisting, and a hospital IT ticket measured in weeks.
You will sign a BAA with the customer and hold BAAs with every subprocessor. AWS, Azure and Google Cloud all sign them, but only cover HIPAA eligible services, and putting PHI in a non-eligible one is on you. Audit logging of who viewed which patient is a regulatory requirement under 45 CFR 164.312(b), not a feature. Then comes the vendor security questionnaire, and increasingly a SOC 2 Type II or HITRUST ask. A Type II needs an observation window, which is calendar time you cannot compress with engineers. Start it before a customer asks.
Test with Synthea generated data, the public HAPI FHIR test server, Inferno for US Core conformance and Touchstone for broader testing. Never real PHI in staging.
What this costs and how long it takes
These are Digital Heroes delivery bands, priced from work we have shipped across 2,000+ projects.
A focused HL7 or FHIR capability inside a product that already exists, one connection, read only, mainstream resources: $15,000 to $60,000. Integration as part of a first release, including auth, ingestion, identity handling and a mapping layer: $50,000 to $130,000 in 10 to 16 weeks. A full multi tenant clinical platform with bidirectional flows: $150,000 to $350,000 over 6 to 12 months.
What pushes this capability specifically up the band:
Every additional health system is its own project. The second Epic site is not free. Plan on 30 to 50 percent of the first connection because the dialect, the Z-segments and the network path are all new. Write access roughly doubles the compliance and testing surface versus read. Real time ADT costs more than a nightly batch because you inherit ordering, ACK semantics and 24/7 on call. Terminology breadth scales with the number of local codes, and lab heavy use cases are the worst. Anything touching orders or medications adds clinical review cycles that are calendar, not code.
When you should not build this
If you are selling to more than about three health systems and your data needs are mainstream (demographics, encounters, labs, medications, problems, notes), do not build direct HL7 v2. Buy the connection. Redox, Health Gorilla, 1upHealth, Particle Health and Metriport exist precisely to absorb the per site weirdness, own the VPN, and hand you one normalized interface. You trade capital cost for a recurring per connection fee and a coverage ceiling, and in our projects that fee has landed in the low five figures per year per health system. That is cheap against a quarter of engineering time per site.
The clear position: a startup with one pilot and a roadmap of twenty hospitals should not write an MLLP listener. Spend the money on the clinical product that actually differentiates you.
Build direct when one of these is true. You need something the aggregator does not carry, such as vendor specific extensions, an in workflow SMART launch inside the EHR, or writeback to a specific resource. One anchor customer is most of your revenue and owns the integration decision. Or your message volume makes per message pricing exceed the build cost inside 18 months. If you own the engine but want transport solved, NextGen Connect is the middle path.
How to brief and vet a developer for this
Brief them with the connection list, not the feature list: which health systems, which EHR versions, read or write, real time or batch, and which resources. That framing alone filters half the field.
Then ask these. "How do you handle ADT^A40?" Silence means they have never been live. "What do you ACK and when, and what is the difference between MSH-15 and MSH-16?" You want durable write before AA. "How do you detect a corrected lab result?" You want OBX-11 equals C and versioned observations. "How do you get change notifications out of FHIR R4?" The correct answer is mostly you do not, here is the v2 feed plus polling plan. "Which US Core version, against which USCDI?" You want a number. "Walk me through your JWKS rotation." "Name three things the sandbox allows that production will not." "How long from signed contract to first message in production?" Anyone who says under six weeks has never waited on hospital IT.
Last one, and it is the fastest filter: ask them to send a de-identified ADT message they have actually parsed, with the segments annotated. Someone who has shipped this has one in a folder and will send it in ten minutes. Everyone else will send a link to the HL7 website.
The evidence behind this guide
Independent findings on why this investment pays off. Every link goes to the primary source.
- The average developer spends more than 17 hours a week dealing with maintenance issues such as debugging and refactoring, and about four of those hours on 'bad code' - waste that equates to nearly $85 billion annually worldwide in opportunity cost. Source: Stripe (2018) →
- McKinsey argues software developer productivity can be measured by combining system-level metrics (DORA and SPACE) with its own outcome-oriented approach, which it reports deploying across nearly 20 tech, finance, and pharmaceutical companies - a claim that sparked significant debate in the engineering community. Source: McKinsey & Company (2023) →
- Nucleus Research's analysis of published analytics deployment case studies found business intelligence and analytics returned an average of $13.01 in benefits for every dollar spent, up from $10.66 three years earlier. Source: Nucleus Research (2014) →
- Flexera's 2025 State of the Cloud Report (survey of 750+ technical and executive leaders) found that 84% of respondents believe managing cloud spend is the top cloud challenge for organizations today, with cloud budgets already exceeding limits by 17%. Source: Flexera (2025) →
Rohan advises mid-market and enterprise teams on ERP, CRM and custom software, and has led delivery on dozens of business-software builds.
Writes for Digital Heroes, shipping business software for 2,000+ brands across 55+ countries since 2017.