<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Om Jariwala</title>
    <description>The latest articles on DEV Community by Om Jariwala (@omjariwala).</description>
    <link>https://dev.to/omjariwala</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4154665%2F7f287aa5-0d95-410d-ae37-0b929feb7bbf.jpg</url>
      <title>DEV Community: Om Jariwala</title>
      <link>https://dev.to/omjariwala</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/omjariwala"/>
    <language>en</language>
    <item>
      <title>Building Telehealth That Handles Latency, EHR Integration, and HIPAA Safeguards</title>
      <dc:creator>Om Jariwala</dc:creator>
      <pubDate>Mon, 19 Oct 2026 08:47:18 +0000</pubDate>
      <link>https://dev.to/omjariwala/building-telehealth-that-handles-latency-ehr-integration-and-hipaa-safeguards-2f59</link>
      <guid>https://dev.to/omjariwala/building-telehealth-that-handles-latency-ehr-integration-and-hipaa-safeguards-2f59</guid>
      <description>&lt;p&gt;A telehealth app can pass every demo and still fail in production. The video freezes on a patient's rural 4G connection. The EHR sync silently drops a medication update. A debug log captures a diagnosis in plain text.&lt;/p&gt;

&lt;p&gt;Healthcare software is judged on a different scale than most products. A glitch isn't just a bad review, it's a clinical risk and a compliance risk. Here are the three problems that cause the most trouble, and how to design for them from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Latency: design for the worst network, not the best
&lt;/h2&gt;

&lt;p&gt;A video visit with a 2-second delay isn't a consultation, it's two people talking over each other. Most of your patients won't be on office fiber.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use WebRTC, but plan for what it doesn't solve.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run TURN servers.&lt;/strong&gt; Direct peer-to-peer fails behind many corporate and mobile NATs. Without TURN fallback, those users simply can't connect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use an SFU for anything beyond 1:1.&lt;/strong&gt; Group visits (patient, doctor, interpreter, family member) don't scale on mesh topologies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy media servers close to users.&lt;/strong&gt; Region-aware routing often does more for perceived quality than any codec tuning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Degrade gracefully.&lt;/strong&gt; When bandwidth drops, reduce video resolution first, then fall back to audio-only. Audio is what carries the consultation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a pre-call check so problems surface before the visit starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;preCallCheck&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;iceServers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;probe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLocalDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOffer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;collectIceStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// RTT, candidate types&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usingRelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedCandidateType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;relay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rttMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rtt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;usingRelay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;recommendation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rtt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio-only&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Measure what matters in production: time-to-first-frame, reconnection rate, packet loss, and join-failure rate. Track them per region and per network type, because averages hide the patients who struggle most.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. EHR integration: assume the other system is slow, strict, and inconsistent
&lt;/h2&gt;

&lt;p&gt;Integrating with EHRs is less about writing API calls and more about surviving their quirks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standardize on FHIR where you can.&lt;/strong&gt; HL7 FHIR R4 with SMART on FHIR (OAuth 2.0) is the most realistic path to interoperability. Expect to still handle HL7 v2 messages and vendor-specific extensions for older systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never block the visit on an EHR call.&lt;/strong&gt; Treat EHR writes as asynchronous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Visit ends -&amp;gt; enqueue, don't call the EHR inline
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_visit_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;encounter.create&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visit-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;build_fhir_encounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Worker: retries with backoff, safe to run twice
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_outbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ehr_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/Encounter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If-None-Exist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;TransientError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retry_with_backoff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_to_dead_letter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# needs human review
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The patterns worth adopting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Outbox pattern.&lt;/strong&gt; Write the clinical record and the sync job in the same database transaction so nothing is lost between them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency keys.&lt;/strong&gt; Retries are guaranteed, so duplicate encounters must be impossible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dead-letter queues with alerting.&lt;/strong&gt; A failed sync of a prescription or note should page someone, not disappear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patient matching with care.&lt;/strong&gt; Matching on name and date of birth alone creates wrong-chart risk. Use the EHR's identifiers (MRN, FHIR Patient IDs) and treat ambiguous matches as a manual review case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map data explicitly.&lt;/strong&gt; Keep a versioned mapping layer between your internal model and FHIR resources, so one EHR's quirk doesn't leak into your whole codebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. HIPAA safeguards: compliance is a system property, not a library
&lt;/h2&gt;

&lt;p&gt;No framework makes you "HIPAA compliant" on its own. Compliance combines technical controls, policies, and agreements. On the technical side, the HIPAA Security Rule's safeguards translate into concrete engineering work:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Safeguard&lt;/th&gt;
&lt;th&gt;What it looks like in practice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Access control&lt;/td&gt;
&lt;td&gt;Role-based access, unique user IDs, MFA, automatic session timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit controls&lt;/td&gt;
&lt;td&gt;Immutable logs of who accessed which record, when, and why&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integrity&lt;/td&gt;
&lt;td&gt;Checksums, versioned records, protection against silent modification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transmission security&lt;/td&gt;
&lt;td&gt;TLS 1.2+ everywhere, DTLS-SRTP for WebRTC media&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption at rest&lt;/td&gt;
&lt;td&gt;Managed keys (KMS), per-environment separation, rotation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few mistakes show up repeatedly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PHI in logs and error trackers.&lt;/strong&gt; Scrub request bodies, and never log full payloads from clinical endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHI in push notifications and emails.&lt;/strong&gt; "Your lab result for diabetes is ready" leaks information on a lock screen. Keep notifications generic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recording by default.&lt;/strong&gt; Store only what you need (the minimum necessary standard), with explicit consent flows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-production environments with real data.&lt;/strong&gt; Use synthetic or de-identified data in dev and staging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing Business Associate Agreements.&lt;/strong&gt; Every vendor that touches PHI (cloud, video, SMS, analytics) needs a BAA, and some tools you rely on may not offer one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple audit log middleware is a good start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/patients/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;finish&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;auditLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;patientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;These three concerns pull against each other. Encryption and audit logging add overhead, EHR sync adds failure modes, and real-time video leaves no room for either. Teams that handle this well do a few things consistently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Decouple real-time from record-keeping.&lt;/strong&gt; The video path stays fast, and clinical documentation flows through reliable async pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build observability in early.&lt;/strong&gt; You can't fix latency or sync failures you can't see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat compliance as part of the architecture review,&lt;/strong&gt; not a checklist before launch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Patients never see any of this. They only notice whether the call connected, whether their doctor had their history, and whether they felt safe. That's what trust in telehealth is built on.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's the hardest part of telehealth engineering you've run into: media quality, EHR quirks, or compliance? Share it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>healthcare</category>
      <category>webdev</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Building EHR Integrations: 6 Things to Get Right Before Writing Code</title>
      <dc:creator>Om Jariwala</dc:creator>
      <pubDate>Wed, 14 Oct 2026 12:11:52 +0000</pubDate>
      <link>https://dev.to/omjariwala/building-ehr-integrations-6-things-to-get-right-before-writing-code-3bbn</link>
      <guid>https://dev.to/omjariwala/building-ehr-integrations-6-things-to-get-right-before-writing-code-3bbn</guid>
      <description>&lt;p&gt;Connecting an application to an Electronic Health Record (EHR) system sounds straightforward.&lt;/p&gt;

&lt;p&gt;Find the API. Authenticate. Fetch patient records. Send updates. Done.&lt;/p&gt;

&lt;p&gt;Except healthcare integrations rarely work that way in practice.&lt;/p&gt;

&lt;p&gt;You might connect successfully but receive incomplete data. A request might time out and create duplicate records when retried. Two systems might use different formats for the same clinical information. Or your application might retrieve patient data without enforcing the right access permissions.&lt;/p&gt;

&lt;p&gt;These problems often start before the first line of integration code is written.&lt;/p&gt;

&lt;p&gt;If you're building a telehealth platform, patient portal, healthcare SaaS product, or clinical application, here are six things to get right before starting an EHR integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Understand the Data Flow Before Choosing the API
&lt;/h2&gt;

&lt;p&gt;Start by identifying what information your application needs and where that information comes from.&lt;/p&gt;

&lt;p&gt;For example, a telehealth platform might need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve patient demographics and medical history.&lt;/li&gt;
&lt;li&gt;Read medications, allergies, and clinical observations.&lt;/li&gt;
&lt;li&gt;Access encounter details.&lt;/li&gt;
&lt;li&gt;Send consultation notes to the EHR.&lt;/li&gt;
&lt;li&gt;Create or update supported clinical records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These workflows can involve different API resources, permissions, and write operations.&lt;/p&gt;

&lt;p&gt;Before implementation, document three things for every data type:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; Which system owns the information?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direction:&lt;/strong&gt; Does your application read, write, or synchronize it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing:&lt;/strong&gt; Does it need to be available immediately, periodically, or only when requested?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This helps establish the integration boundary and prevents the application from treating every connected system as an interchangeable database.&lt;/p&gt;

&lt;p&gt;A useful rule: define the workflow first, then identify the API operations needed to support it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understand FHIR and the EHR's Actual Implementation
&lt;/h2&gt;

&lt;p&gt;FHIR (Fast Healthcare Interoperability Resources) provides a standard for exchanging healthcare information.&lt;/p&gt;

&lt;p&gt;It defines resources such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Patient&lt;/code&gt; — patient demographics and identifying information.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Observation&lt;/code&gt; — measurements and clinical observations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Encounter&lt;/code&gt; — a healthcare interaction.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Condition&lt;/code&gt; — a health condition or diagnosis.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MedicationRequest&lt;/code&gt; — a medication order or prescription request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DiagnosticReport&lt;/code&gt; — diagnostic findings and report information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FHIR makes interoperability easier, but it doesn't guarantee that every EHR implements every resource in the same way.&lt;/p&gt;

&lt;p&gt;One vendor may support a particular resource but restrict certain operations. Another may require specific profiles, search parameters, or authorization scopes.&lt;/p&gt;

&lt;p&gt;Before writing code, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which FHIR version is supported?&lt;/li&gt;
&lt;li&gt;Which resources and operations are available?&lt;/li&gt;
&lt;li&gt;Are there vendor-specific implementation guides?&lt;/li&gt;
&lt;li&gt;Which fields are mandatory?&lt;/li&gt;
&lt;li&gt;What search parameters and pagination behavior are supported?&lt;/li&gt;
&lt;li&gt;Which authorization scopes are required?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also review the vendor's sandbox and test data, where available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't build against the FHIR specification alone. Build against the specification and the target EHR's implementation.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Design Patient Matching and Data Mapping Carefully
&lt;/h2&gt;

&lt;p&gt;One of the most important integration decisions is determining whether two records represent the same patient.&lt;/p&gt;

&lt;p&gt;Your application may use an internal patient ID, while the EHR uses its own identifier. Names and dates of birth alone may not reliably establish identity.&lt;/p&gt;

&lt;p&gt;A safer design uses the appropriate identifiers and matching rules agreed upon for the integration.&lt;/p&gt;

&lt;p&gt;For example, maintain a mapping between your internal patient record and the corresponding identifier in the external system. Account for multiple identifiers where the workflow requires them.&lt;/p&gt;

&lt;p&gt;Data mapping deserves the same attention.&lt;/p&gt;

&lt;p&gt;A field in your application may not correspond directly to a field in the EHR. Clinical concepts may use standardized codes, specific units, or vendor-defined profiles.&lt;/p&gt;

&lt;p&gt;Before implementing transformations, establish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identifier mapping rules.&lt;/li&gt;
&lt;li&gt;Field-level mappings.&lt;/li&gt;
&lt;li&gt;Required and optional values.&lt;/li&gt;
&lt;li&gt;Terminology and unit handling.&lt;/li&gt;
&lt;li&gt;Missing or conflicting data behavior.&lt;/li&gt;
&lt;li&gt;Validation rules for incoming and outgoing records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid silently filling gaps with assumptions.&lt;/p&gt;

&lt;p&gt;If a record cannot be matched confidently, the system should follow a defined exception-handling process rather than attaching the information to a potentially incorrect patient.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Plan Authentication, Authorization, and Audit Logging
&lt;/h2&gt;

&lt;p&gt;An integration that can retrieve patient information must also enforce appropriate access restrictions.&lt;/p&gt;

&lt;p&gt;Depending on the EHR and application, authentication may use OAuth 2.0, SMART on FHIR, or another supported mechanism.&lt;/p&gt;

&lt;p&gt;But successful authentication doesn't mean every request is authorized.&lt;/p&gt;

&lt;p&gt;Consider these implementation details:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store credentials securely.&lt;/li&gt;
&lt;li&gt;Handle token expiration and refresh where supported.&lt;/li&gt;
&lt;li&gt;Avoid exposing access tokens in logs or client-side code.&lt;/li&gt;
&lt;li&gt;Request only the scopes the application needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Authorization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enforce permissions on the server.&lt;/li&gt;
&lt;li&gt;Verify access to the requested patient and resource.&lt;/li&gt;
&lt;li&gt;Separate user-authorized access from background service operations.&lt;/li&gt;
&lt;li&gt;Handle revoked access and failed authorization explicitly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Audit logging&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Record relevant access and modification events.&lt;/li&gt;
&lt;li&gt;Capture enough context to investigate problems.&lt;/li&gt;
&lt;li&gt;Restrict access to audit logs.&lt;/li&gt;
&lt;li&gt;Avoid unnecessarily copying protected health information into log messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For US healthcare applications subject to HIPAA, these controls should be designed in the context of the applicable security and privacy requirements. An API integration alone does not establish compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make Retries Safe and Handle Failures Explicitly
&lt;/h2&gt;

&lt;p&gt;External systems can be slow or unavailable. Requests can time out, rate limits can be reached, and network connections can fail.&lt;/p&gt;

&lt;p&gt;The difficult part is that a timeout doesn't always tell you whether the operation succeeded.&lt;/p&gt;

&lt;p&gt;Imagine your application sends a request to create an encounter-related record. The EHR processes it, but the response never reaches your application.&lt;/p&gt;

&lt;p&gt;Your application retries the request.&lt;/p&gt;

&lt;p&gt;Without suitable safeguards, you may create a duplicate.&lt;/p&gt;

&lt;p&gt;A robust integration should consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts:&lt;/strong&gt; Avoid requests waiting indefinitely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry policies:&lt;/strong&gt; Retry transient failures with bounded exponential backoff and jitter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limits:&lt;/strong&gt; Respect vendor limits and retry guidance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency:&lt;/strong&gt; Use supported idempotency mechanisms or another reliable duplicate-prevention strategy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error classification:&lt;/strong&gt; Distinguish temporary failures from invalid requests and authorization errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconciliation:&lt;/strong&gt; Detect and resolve records that may have been missed or processed inconsistently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every API supports idempotency keys, so the implementation must follow the actual vendor's capabilities.&lt;/p&gt;

&lt;p&gt;For workflows that can tolerate asynchronous processing, background jobs and queues can help separate user-facing requests from external-system availability.&lt;/p&gt;

&lt;p&gt;The goal isn't to prevent every failure. It's to make failures predictable and recoverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Test the Complete Workflow, Not Just Individual Endpoints
&lt;/h2&gt;

&lt;p&gt;A successful API response proves that one request worked. It doesn't prove that the entire healthcare workflow is correct.&lt;/p&gt;

&lt;p&gt;Suppose your application can retrieve a patient record successfully. You still need to validate what happens when the record is incomplete, the patient has multiple identifiers, authorization changes, or a downstream system becomes unavailable.&lt;/p&gt;

&lt;p&gt;A useful test plan includes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test area&lt;/th&gt;
&lt;th&gt;What to validate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data mapping&lt;/td&gt;
&lt;td&gt;Fields, identifiers, codes, and units are mapped correctly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization&lt;/td&gt;
&lt;td&gt;Users and services cannot access unauthorized records.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pagination&lt;/td&gt;
&lt;td&gt;Large result sets are processed without missing records.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error handling&lt;/td&gt;
&lt;td&gt;Timeouts, rate limits, and server errors are handled safely.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate prevention&lt;/td&gt;
&lt;td&gt;Retried operations don't create unintended duplicates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data freshness&lt;/td&gt;
&lt;td&gt;Updates and synchronization delays are handled as expected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit logging&lt;/td&gt;
&lt;td&gt;Relevant access and modification events are recorded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-end workflows&lt;/td&gt;
&lt;td&gt;Data moves correctly across the complete user journey.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use vendor sandboxes and synthetic test data where available. Test against realistic edge cases, not only successful responses.&lt;/p&gt;

&lt;p&gt;Also verify what happens when an integration is disconnected, credentials expire, or a vendor changes its API behavior.&lt;/p&gt;

&lt;p&gt;These scenarios often reveal issues that simple endpoint tests miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Checklist Before Development
&lt;/h2&gt;

&lt;p&gt;Before committing to an EHR integration, make sure you can answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Do we know which workflows the integration must support?&lt;/li&gt;
&lt;li&gt;[ ] Have we confirmed the target EHR's API capabilities and supported FHIR version?&lt;/li&gt;
&lt;li&gt;[ ] Have we documented data mappings and patient-matching rules?&lt;/li&gt;
&lt;li&gt;[ ] Are authentication scopes and authorization rules defined?&lt;/li&gt;
&lt;li&gt;[ ] Do we have a plan for timeouts, retries, duplicate prevention, and reconciliation?&lt;/li&gt;
&lt;li&gt;[ ] Have we defined integration tests and end-to-end acceptance criteria?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If several answers are still unclear, the next step is requirements discovery, not more code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;EHR integration is not simply an API task. It sits at the intersection of data modeling, identity, security, clinical workflows, and distributed-system reliability.&lt;/p&gt;

&lt;p&gt;FHIR gives development teams a shared foundation, but reliable interoperability still depends on understanding the systems on both sides of the connection.&lt;/p&gt;

&lt;p&gt;The best time to discover an identifier mismatch, missing permission, or unsupported API operation is before the integration becomes a dependency for your entire product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Map the workflow. Verify the API. Define the data rules. Plan for failure. Then start coding.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building a healthcare product, these decisions can make the difference between an integration that works in a demo and one that holds up in real-world use.&lt;/p&gt;

</description>
      <category>healthcare</category>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your Healthcare Voice AI Agent Needs a Compliance Gate (Before It Costs You $50,000)</title>
      <dc:creator>Om Jariwala</dc:creator>
      <pubDate>Tue, 13 Oct 2026 05:21:30 +0000</pubDate>
      <link>https://dev.to/omjariwala/your-healthcare-voice-ai-agent-needs-a-compliance-gate-before-it-costs-you-50000-19c</link>
      <guid>https://dev.to/omjariwala/your-healthcare-voice-ai-agent-needs-a-compliance-gate-before-it-costs-you-50000-19c</guid>
      <description>&lt;p&gt;An $800 voice agent cost a healthcare founder $50,000.&lt;/p&gt;

&lt;p&gt;I heard this directly from him last week. The agent worked. The voice was natural and the integration was clean. It had no rules for how often it could call the same patient or during which hours. It called the same people repeatedly, some calls landed outside permitted hours, and one complaint turned into a $50,000 bill.&lt;/p&gt;

&lt;p&gt;Nothing crashed, and no model misbehaved. This was an &lt;strong&gt;engineering omission&lt;/strong&gt;, and it's the kind we can prevent.&lt;/p&gt;

&lt;p&gt;Here's how to design voice agents so that doesn't happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core principle: the model never decides if a call is allowed
&lt;/h2&gt;

&lt;p&gt;LLMs are good at conversation. They are a bad place to enforce legal constraints. A prompt that says "don't call more than once a day" is a suggestion, not a control.&lt;/p&gt;

&lt;p&gt;Compliance has to live in &lt;strong&gt;deterministic code that runs before the agent is ever invoked.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trigger (form abandoned / checkout dropped)
        │
        ▼
 ┌──────────────────┐
 │  Call scheduler  │  queue + delay (e.g. 15-20 min)
 └────────┬─────────┘
          ▼
 ┌──────────────────┐
 │ COMPLIANCE GATE  │  consent, DNC, time window,
 │ (deterministic)  │  frequency caps, call type
 └────────┬─────────┘
     deny │ allow
   ┌──────┘   │
   ▼          ▼
 requeue   Voice agent ──► tools (CRM/EHR, SMS, human handoff)
 or drop        │
                ▼
          Audit log (every decision, every call)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the gate says no, the call is requeued for the next valid window or dropped. The agent never gets a vote.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;

&lt;span class="n"&gt;MAX_CALLS_PER_DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;MAX_CALLS_PER_WEEK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="c1"&gt;# Conservative window. Federal rules set the outer limit and
# some states are stricter. Confirm with counsel.
&lt;/span&gt;&lt;span class="n"&gt;WINDOW_START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WINDOW_END&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;  &lt;span class="c1"&gt;# recipient-local hours
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now_utc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;now_utc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UTC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_not_call&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_consent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_consent_or_dnc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WINDOW_START&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;WINDOW_END&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outside_calling_window&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;day_ago&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;week_ago&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;call_log&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mine&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;day_ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_CALLS_PER_DAY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daily_cap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mine&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;week_ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_CALLS_PER_WEEK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly_cap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details that matter more than they look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use the recipient's timezone, not the server's.&lt;/strong&gt; A form filled at 3 a.m. their time gets queued, not called.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pass &lt;code&gt;call_type&lt;/code&gt;.&lt;/strong&gt; Marketing and transactional calls have different consent rules and should use separate numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return a reason code.&lt;/strong&gt; You'll want it in the audit log.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Concurrency: your caps are only as good as your locking
&lt;/h2&gt;

&lt;p&gt;A naive check-then-call has a race condition. Two workers pick up the same patient at the same moment, both see zero calls, and both dial.&lt;/p&gt;

&lt;p&gt;Make the check and the "reserve a slot" step atomic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudocode: reserve before dialing, release on failure
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;can_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;call_log_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;call_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;denied&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;call_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reserved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# dial only after the reservation commits
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a unique constraint or row-level lock per patient so duplicate triggers (webhook retries are common) can't cause duplicate calls. Make your trigger handlers &lt;strong&gt;idempotent&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep PHI out of the conversation
&lt;/h2&gt;

&lt;p&gt;Personalization is what makes these agents effective. The agent that knows where the patient left off converts far better than a generic script. But the personalization layer is also where privacy leaks happen.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Give the agent only what it needs.&lt;/strong&gt; First name and form progress, not the full record. Don't put diagnoses in the prompt or tool results unless required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add an output check.&lt;/strong&gt; Scan agent responses for restricted content before they reach speech, and have the agent fall back to "I can't discuss that on this call" on a match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redact logs and transcripts.&lt;/strong&gt; Call recordings and transcripts are PHI too. Treat them that way in storage and access control.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Audit everything
&lt;/h2&gt;

&lt;p&gt;When a complaint arrives, your answer depends on your records. Log:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every gate decision, with the reason code&lt;/li&gt;
&lt;li&gt;Consent source and timestamp&lt;/li&gt;
&lt;li&gt;Call start, end, outcome, and which number was used&lt;/li&gt;
&lt;li&gt;Handoffs to a human&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can't show why a call was allowed, you can't defend it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BAA coverage for every component&lt;/strong&gt; that touches patient data: LLM provider, voice platform, telephony, transcription, storage, logging. Some vendors only offer coverage on certain tiers or via add-ons, so verify each one in writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branded, verified numbers&lt;/strong&gt; to avoid spam labeling, and separate numbers for marketing and transactional calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant opt-out handling&lt;/strong&gt; that updates the consent store immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human handoff&lt;/strong&gt; available on every call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Does it work? A real deployment
&lt;/h2&gt;

&lt;p&gt;We built this pattern for a US telehealth company that acquires patients through paid social ads. A long intake form plus a payment step meant many people dropped off before paying.&lt;/p&gt;

&lt;p&gt;The agent calls roughly 15 to 20 minutes after drop-off. It answers questions using the patient's form progress (pulled from their EHR), sends checkout links or discounts by SMS or email, shares testimonials, and hands off to a human on request.&lt;/p&gt;

&lt;p&gt;It recovered about &lt;strong&gt;10% of abandoned checkouts&lt;/strong&gt; and was rolled out across multiple brands. The gate, caps, privacy controls, and audit log are what let it run at that scale without incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Integration is easy. Compliant integration is the actual project.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never let the model enforce legal rules.&lt;/strong&gt; Put a deterministic gate in front of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make caps race-proof&lt;/strong&gt; with atomic reservations and idempotent triggers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimize and redact PHI&lt;/strong&gt; in prompts, outputs, and logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify BAA coverage&lt;/strong&gt; across the whole stack, in writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hire for domain experience&lt;/strong&gt;, not just the lowest quote. The $800 build is rarely the real price.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;I build compliant AI and telehealth systems at &lt;a href="https://www.enacton.com" rel="noopener noreferrer"&gt;Enacton&lt;/a&gt;, with HIPAA and calling-rule controls designed in from day one. Planning a voice AI project in healthcare? Let's talk before you launch, not after.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>healthcare</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>In 2026, These 6 Factors Determine Whether Your Epic Integration Costs $25K or $250K+</title>
      <dc:creator>Om Jariwala</dc:creator>
      <pubDate>Fri, 09 Oct 2026 10:07:24 +0000</pubDate>
      <link>https://dev.to/omjariwala/in-2026-these-6-factors-determine-whether-your-epic-integration-costs-25k-or-250k-njb</link>
      <guid>https://dev.to/omjariwala/in-2026-these-6-factors-determine-whether-your-epic-integration-costs-25k-or-250k-njb</guid>
      <description>&lt;p&gt;If you're building a healthcare SaaS product, integrating with Epic EHR is a challenge you may eventually need to solve.&lt;/p&gt;

&lt;p&gt;At first, it might sound straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect to Epic's APIs.&lt;/li&gt;
&lt;li&gt;Fetch patient data.&lt;/li&gt;
&lt;li&gt;Map it to your application's data model.&lt;/li&gt;
&lt;li&gt;Ship the integration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But production healthcare integrations rarely stop there.&lt;/p&gt;

&lt;p&gt;You'll need to account for authentication, FHIR resource mapping, clinical workflows, customer-specific configuration, security reviews, testing, and ongoing maintenance.&lt;/p&gt;

&lt;p&gt;Depending on the scope, an Epic integration can become a substantial engineering project.&lt;/p&gt;

&lt;p&gt;So, what actually drives the cost?&lt;/p&gt;

&lt;p&gt;Let's break it down from a software engineering perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, What Should You Budget?
&lt;/h2&gt;

&lt;p&gt;The following ranges are illustrative planning estimates, not official Epic pricing or independently verified market averages.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Integration scope&lt;/th&gt;
&lt;th&gt;Illustrative budget&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic, read-only data integration&lt;/td&gt;
&lt;td&gt;$25K–$50K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple resources and workflows&lt;/td&gt;
&lt;td&gt;$50K–$100K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex, bidirectional clinical workflows&lt;/td&gt;
&lt;td&gt;$100K–$250K+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These estimates assume a project-based implementation. Actual costs depend on engineering rates, the number of workflows, customer requirements, deployment complexity, and the scope of production support.&lt;/p&gt;

&lt;p&gt;The important distinction is that &lt;strong&gt;connecting to an API is not the same as delivering a production-ready EHR integration.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are the six factors that influence the engineering effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. FHIR Resource Coverage and Data Mapping
&lt;/h2&gt;

&lt;p&gt;The first question is: What data does your application actually need?&lt;/p&gt;

&lt;p&gt;A basic integration might retrieve patient demographics. A more complex integration could require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Patient&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Practitioner&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Encounter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Condition&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MedicationRequest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AllergyIntolerance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Observation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DiagnosticReport&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Appointment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DocumentReference&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FHIR provides standardized resource models, but it doesn't eliminate implementation differences between systems.&lt;/p&gt;

&lt;p&gt;Your application still needs to understand how the source data maps to your internal domain model.&lt;/p&gt;

&lt;p&gt;For example, an &lt;code&gt;Observation&lt;/code&gt; resource might represent a vital sign, a laboratory result, or another clinical measurement. Your application needs to interpret its codes, units, status, timestamps, and references correctly for the intended workflow.&lt;/p&gt;

&lt;p&gt;You may also encounter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing or optional fields&lt;/li&gt;
&lt;li&gt;Different coding systems&lt;/li&gt;
&lt;li&gt;References to other resources&lt;/li&gt;
&lt;li&gt;Pagination and large result sets&lt;/li&gt;
&lt;li&gt;Customer-specific configuration&lt;/li&gt;
&lt;li&gt;Differences in supported FHIR capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What does this mean for cost?
&lt;/h3&gt;

&lt;p&gt;Every additional resource type can introduce mapping, validation, and testing work.&lt;/p&gt;

&lt;p&gt;The complexity increases further when resources are linked together and your application needs to preserve clinical meaning across those relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering takeaway:&lt;/strong&gt; Define the required resources, fields, relationships, and expected data quality before estimating the integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Read-Only Access vs. Bidirectional Workflows
&lt;/h2&gt;

&lt;p&gt;There is a significant difference between reading data from an EHR and writing data back to it.&lt;/p&gt;

&lt;p&gt;A read-only integration might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Epic
  |
  | FHIR API
  v
Integration Service
  |
  | Normalize and validate
  v
Healthcare SaaS Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your service retrieves authorized data, transforms it into your internal model, and makes it available to your application.&lt;/p&gt;

&lt;p&gt;Now consider a bidirectional workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Epic &amp;lt;----&amp;gt; Integration Service &amp;lt;----&amp;gt; SaaS Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application may need to create or update supported resources, depending on the use case and the customer's configuration.&lt;/p&gt;

&lt;p&gt;That introduces additional engineering concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write permissions and authorization scopes&lt;/li&gt;
&lt;li&gt;Validation before submitting data&lt;/li&gt;
&lt;li&gt;Handling rejected or partially processed requests&lt;/li&gt;
&lt;li&gt;Duplicate submissions and retry behavior&lt;/li&gt;
&lt;li&gt;Idempotency where applicable&lt;/li&gt;
&lt;li&gt;Conflict detection and reconciliation&lt;/li&gt;
&lt;li&gt;Audit trails and failure recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, suppose your application submits a clinical observation, but the request times out before your service receives a response.&lt;/p&gt;

&lt;p&gt;Did Epic process the request?&lt;/p&gt;

&lt;p&gt;Blindly retrying could create duplicate data if the operation isn't safely idempotent.&lt;/p&gt;

&lt;p&gt;Your integration needs an explicit strategy for uncertain outcomes.&lt;/p&gt;

&lt;p&gt;Also, don't assume every FHIR resource supports every operation. Available operations, permissions, and implementation behavior depend on the relevant Epic environment and API configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering takeaway:&lt;/strong&gt; Estimate each workflow separately. Read operations, write operations, and synchronization are different engineering problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. FHIR Doesn't Eliminate Authentication and Interoperability Work
&lt;/h2&gt;

&lt;p&gt;Using FHIR is a good starting point, but it doesn't make every EHR integration plug-and-play.&lt;/p&gt;

&lt;p&gt;Your team still needs to implement the authentication and authorization flow required by the specific integration.&lt;/p&gt;

&lt;p&gt;Depending on the application and environment, this may involve OAuth 2.0, SMART on FHIR, or other supported authorization patterns.&lt;/p&gt;

&lt;p&gt;Typical implementation concerns include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Obtaining and refreshing access tokens where applicable&lt;/li&gt;
&lt;li&gt;Managing token expiration&lt;/li&gt;
&lt;li&gt;Requesting the appropriate scopes&lt;/li&gt;
&lt;li&gt;Handling &lt;code&gt;401 Unauthorized&lt;/code&gt; and &lt;code&gt;403 Forbidden&lt;/code&gt; responses&lt;/li&gt;
&lt;li&gt;Respecting API limits and retry guidance&lt;/li&gt;
&lt;li&gt;Validating resource schemas&lt;/li&gt;
&lt;li&gt;Supporting pagination&lt;/li&gt;
&lt;li&gt;Handling &lt;code&gt;4xx&lt;/code&gt; and &lt;code&gt;5xx&lt;/code&gt; responses&lt;/li&gt;
&lt;li&gt;Logging failures without exposing protected health information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A robust integration also needs observability.&lt;/p&gt;

&lt;p&gt;For example, track request failures, latency, retry counts, synchronization delays, and resource-processing errors.&lt;/p&gt;

&lt;p&gt;Avoid logging access tokens or sensitive patient data simply to make debugging easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does this mean for cost?
&lt;/h3&gt;

&lt;p&gt;A prototype may only need a successful API request.&lt;/p&gt;

&lt;p&gt;A production service needs predictable behavior when tokens expire, requests fail, upstream services become unavailable, or data arrives in an unexpected format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering takeaway:&lt;/strong&gt; Budget for failure handling and observability, not just the happy path.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Customer Onboarding and Environment-Specific Configuration
&lt;/h2&gt;

&lt;p&gt;Building the integration is only part of delivering it to a healthcare customer.&lt;/p&gt;

&lt;p&gt;The implementation may also require environment-specific configuration, technical coordination, testing, and customer approval.&lt;/p&gt;

&lt;p&gt;Depending on the project, this can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirming API and resource availability&lt;/li&gt;
&lt;li&gt;Configuring application access&lt;/li&gt;
&lt;li&gt;Coordinating authentication and permissions&lt;/li&gt;
&lt;li&gt;Completing security and technical reviews&lt;/li&gt;
&lt;li&gt;Validating data mappings&lt;/li&gt;
&lt;li&gt;Testing against the customer's environment&lt;/li&gt;
&lt;li&gt;Supporting user acceptance testing&lt;/li&gt;
&lt;li&gt;Coordinating production deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A workflow that works in a development environment isn't automatically ready for a customer's production environment.&lt;/p&gt;

&lt;p&gt;The customer may have different configuration requirements, permissions, data availability, or workflow expectations.&lt;/p&gt;

&lt;p&gt;If you support multiple customers, you also need to decide which parts of the integration are reusable and which require customer-specific configuration.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shared Integration Platform
    |
    +-- Customer A configuration
    |
    +-- Customer B configuration
    |
    +-- Customer C configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A configurable architecture can reduce duplicated work. However, customer onboarding, validation, and troubleshooting may still require dedicated effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering takeaway:&lt;/strong&gt; Separate the cost of building the integration platform from the cost of implementing and supporting each customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Security, Privacy, and Compliance Requirements
&lt;/h2&gt;

&lt;p&gt;Healthcare integrations handle sensitive data, so security needs to be part of the architecture from the beginning.&lt;/p&gt;

&lt;p&gt;Depending on your role, data flows, and applicable obligations, the project may require controls such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encryption in transit and at rest&lt;/li&gt;
&lt;li&gt;Least-privilege access controls&lt;/li&gt;
&lt;li&gt;Secure credential and secret management&lt;/li&gt;
&lt;li&gt;Audit logging&lt;/li&gt;
&lt;li&gt;Data retention and deletion policies&lt;/li&gt;
&lt;li&gt;Tenant isolation&lt;/li&gt;
&lt;li&gt;Incident detection and response&lt;/li&gt;
&lt;li&gt;Secure development and deployment practices&lt;/li&gt;
&lt;li&gt;Appropriate contractual and compliance documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For US healthcare use cases, HIPAA-related requirements may also apply. The exact obligations depend on the organizations involved, the data being handled, and the nature of the service.&lt;/p&gt;

&lt;p&gt;These requirements affect more than infrastructure.&lt;/p&gt;

&lt;p&gt;They can influence application architecture, database design, logging, monitoring, access policies, testing, and operational procedures.&lt;/p&gt;

&lt;p&gt;For example, a centralized logging system can be useful for troubleshooting, but it should not inadvertently collect unnecessary protected health information.&lt;/p&gt;

&lt;p&gt;Similarly, a multi-tenant SaaS platform needs a clear strategy for preventing one customer's data from becoming accessible to another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering takeaway:&lt;/strong&gt; Include security engineering, compliance-related implementation, and verification in the project estimate rather than treating them as post-launch tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Testing, Production Operations, and Long-Term Maintenance
&lt;/h2&gt;

&lt;p&gt;The integration isn't finished when the first successful request reaches production.&lt;/p&gt;

&lt;p&gt;You still need to verify that it behaves correctly under real operating conditions.&lt;/p&gt;

&lt;p&gt;A production testing strategy might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests for resource mapping&lt;/li&gt;
&lt;li&gt;Integration tests for API interactions&lt;/li&gt;
&lt;li&gt;Contract and schema validation&lt;/li&gt;
&lt;li&gt;Authorization and permission tests&lt;/li&gt;
&lt;li&gt;Negative tests for invalid requests&lt;/li&gt;
&lt;li&gt;Retry and timeout tests&lt;/li&gt;
&lt;li&gt;Duplicate-processing tests&lt;/li&gt;
&lt;li&gt;Regression tests for existing workflows&lt;/li&gt;
&lt;li&gt;End-to-end workflow validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clinical data mapping deserves particular attention.&lt;/p&gt;

&lt;p&gt;A request can return a technically valid FHIR response while your application still interprets the underlying data incorrectly.&lt;/p&gt;

&lt;p&gt;Testing therefore needs to verify both technical correctness and the expected behavior of the application workflow.&lt;/p&gt;

&lt;p&gt;After launch, ongoing work may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitoring failed requests&lt;/li&gt;
&lt;li&gt;Investigating customer-reported issues&lt;/li&gt;
&lt;li&gt;Updating dependencies&lt;/li&gt;
&lt;li&gt;Reviewing API and configuration changes&lt;/li&gt;
&lt;li&gt;Maintaining compatibility&lt;/li&gt;
&lt;li&gt;Responding to security findings&lt;/li&gt;
&lt;li&gt;Supporting additional resources and workflows&lt;/li&gt;
&lt;li&gt;Revalidating behavior after code changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every project will require the same maintenance effort, but none should assume that production support is free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Think in terms of total cost of ownership
&lt;/h3&gt;

&lt;p&gt;Instead of estimating only the initial development effort, use a broader model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total Integration Cost =
Initial Engineering + Testing and Validation + Security and Compliance Work + Customer 
Implementation + Go-Live Support + Ongoing Maintenance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a planning framework, not a literal pricing formula. Each category should be estimated using the actual scope and expected effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering takeaway:&lt;/strong&gt; Distinguish one-time implementation costs from recurring operational costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Estimate an Epic Integration More Accurately
&lt;/h2&gt;

&lt;p&gt;Before assigning a budget, document the integration scope.&lt;/p&gt;

&lt;p&gt;A practical starting checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ]  &lt;strong&gt;Data scope:&lt;/strong&gt; Which FHIR resources and fields are required?&lt;/li&gt;
&lt;li&gt;[ ]  &lt;strong&gt;Workflow scope:&lt;/strong&gt; Is the integration read-only, write-enabled, or bidirectional?&lt;/li&gt;
&lt;li&gt;[ ]  &lt;strong&gt;Authorization:&lt;/strong&gt; Which authentication flow, scopes, and permissions are needed?&lt;/li&gt;
&lt;li&gt;[ ]  &lt;strong&gt;Data mapping:&lt;/strong&gt; What transformations, terminology mappings, and validations are required?&lt;/li&gt;
&lt;li&gt;[ ]  &lt;strong&gt;Customer scope:&lt;/strong&gt; How many environments and customer-specific configurations must be supported?&lt;/li&gt;
&lt;li&gt;[ ]  &lt;strong&gt;Security:&lt;/strong&gt; Which technical controls and compliance obligations apply?&lt;/li&gt;
&lt;li&gt;[ ]  &lt;strong&gt;Testing:&lt;/strong&gt; What integration, regression, and end-to-end tests are required?&lt;/li&gt;
&lt;li&gt;[ ]  &lt;strong&gt;Operations:&lt;/strong&gt; Who owns monitoring, incident response, and ongoing maintenance?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once these requirements are clear, estimate the effort for each workstream.&lt;/p&gt;

&lt;p&gt;You can then build a budget from engineering hours, implementation costs, external dependencies, contingency, and expected operational expenses.&lt;/p&gt;

&lt;p&gt;This is much more useful than assigning a price based solely on the fact that the project uses FHIR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The biggest estimation mistake is treating an Epic integration as a single API development task.&lt;/p&gt;

&lt;p&gt;In reality, the project can involve several interconnected workstreams:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FHIR implementation + Authentication + Data Mapping + Workflow Engineering + Security + Customer Onboarding + Testing + Maintenance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A narrowly scoped integration may fit within a relatively modest budget. A production implementation with multiple workflows, write-back capabilities, customer-specific requirements, and ongoing support can demand a substantially larger investment.&lt;/p&gt;

&lt;p&gt;The right estimate depends on what you're building, which capabilities are available in the target environment, and what it takes to operate the integration reliably.&lt;/p&gt;

&lt;p&gt;So instead of asking:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“How much does an Epic integration cost?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“What will it take to build, deploy, validate, and maintain our specific Epic integration in production?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That question gives your engineering team a much better starting point for estimating both cost and delivery time.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;A question for healthcare developers and engineering teams:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a production Epic integration, which part tends to consume the most engineering effort in your experience: FHIR resource mapping, authentication, customer onboarding, security reviews, or long-term maintenance?&lt;/p&gt;

&lt;p&gt;I'd be interested in hearing how your team approaches estimation and what costs are easiest to underestimate.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>healthcare</category>
      <category>epicintegration</category>
      <category>software</category>
    </item>
    <item>
      <title>From Prompt-and-Response to Agentic Workflows: Engineering an AI Content Platform for Telehealth</title>
      <dc:creator>Om Jariwala</dc:creator>
      <pubDate>Thu, 08 Oct 2026 07:20:31 +0000</pubDate>
      <link>https://dev.to/omjariwala/from-prompt-and-response-to-agentic-workflows-engineering-an-ai-content-platform-for-telehealth-4gjk</link>
      <guid>https://dev.to/omjariwala/from-prompt-and-response-to-agentic-workflows-engineering-an-ai-content-platform-for-telehealth-4gjk</guid>
      <description>&lt;p&gt;A healthcare company came to us with an internal AI-powered SEO platform.&lt;/p&gt;

&lt;p&gt;The prototype already worked.&lt;/p&gt;

&lt;p&gt;It could research topics, generate content, perform SEO checks and publish to WordPress.&lt;/p&gt;

&lt;p&gt;The problem was scalability.&lt;/p&gt;

&lt;p&gt;As the system grew, it became increasingly difficult to maintain. The architecture relied too heavily on a single AI provider, browser-side state and workflows that were not designed for multiple brands.&lt;/p&gt;

&lt;p&gt;We needed to turn the prototype into production software without losing the workflow that had already been validated.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The original architecture had reached its limits
&lt;/h2&gt;

&lt;p&gt;The initial system was built around a relatively simple interaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Input
    ↓
LLM
    ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works well for experimentation.&lt;/p&gt;

&lt;p&gt;It becomes limiting when a workflow needs to remember previous decisions and pass structured context between multiple stages.&lt;/p&gt;

&lt;p&gt;The new workflow looked more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Topic Discovery
      ↓
Research
      ↓
Content Planning
      ↓
Content Generation
      ↓
SEO Evaluation
      ↓
AEO/GEO Evaluation
      ↓
Human Review
      ↓
Publishing
      ↓
Feedback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage had a specific responsibility.&lt;/p&gt;

&lt;p&gt;The system was no longer treating the LLM as one general-purpose assistant.&lt;/p&gt;

&lt;p&gt;It was becoming an orchestration layer around multiple specialized tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Moving away from a single-model dependency
&lt;/h2&gt;

&lt;p&gt;The original platform depended directly on one LLM provider.&lt;/p&gt;

&lt;p&gt;That creates a practical engineering problem.&lt;/p&gt;

&lt;p&gt;A model change, pricing change, availability issue or capability change can suddenly become an application-level problem.&lt;/p&gt;

&lt;p&gt;We moved the AI layer to OpenRouter so the application could work with multiple models through a common integration.&lt;/p&gt;

&lt;p&gt;An image-generation routing layer was also added.&lt;/p&gt;

&lt;p&gt;The goal wasn't to use more models for the sake of using more models.&lt;/p&gt;

&lt;p&gt;It was to make the application layer less dependent on one provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Persistent context became part of the workflow
&lt;/h2&gt;

&lt;p&gt;A multi-step content system needs memory of what happened earlier in the workflow.&lt;/p&gt;

&lt;p&gt;For example, when evaluating a new article, the system needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What content already exists?&lt;/li&gt;
&lt;li&gt;Has this topic already been covered?&lt;/li&gt;
&lt;li&gt;What did the research stage find?&lt;/li&gt;
&lt;li&gt;What did previous evaluation stages identify?&lt;/li&gt;
&lt;li&gt;What feedback has the human reviewer provided?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that context, every stage becomes another isolated AI request.&lt;/p&gt;

&lt;p&gt;The architecture therefore shifted toward persistent workflow context.&lt;/p&gt;

&lt;p&gt;This allowed different agents to work on different stages while maintaining continuity.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. AI-generated content still needed human evaluation
&lt;/h2&gt;

&lt;p&gt;Healthcare is not a domain where increasing generation volume is enough.&lt;/p&gt;

&lt;p&gt;The platform was designed to support high-volume content production while retaining a human review layer.&lt;/p&gt;

&lt;p&gt;SEO specialists and writers could provide natural-language feedback.&lt;/p&gt;

&lt;p&gt;That feedback could then become part of the system's context.&lt;/p&gt;

&lt;p&gt;The objective was not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI → Publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI
 ↓
Evaluation
 ↓
Human Review
 ↓
Feedback
 ↓
Improved Context
 ↓
Next Generation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Supporting multiple publishing environments
&lt;/h2&gt;

&lt;p&gt;The platform eventually needed to support both WordPress and Astro-based websites.&lt;/p&gt;

&lt;p&gt;WordPress provided an existing CMS workflow.&lt;/p&gt;

&lt;p&gt;Astro introduced a different challenge because content specialists needed a CMS-style interface even though the underlying sites were using a more developer-oriented architecture.&lt;/p&gt;

&lt;p&gt;The solution was to add CMS capabilities to the platform itself while maintaining compatibility with WordPress.&lt;/p&gt;

&lt;p&gt;Custom WordPress components were also required for specific generated content such as HTML-based infographics.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The result
&lt;/h2&gt;

&lt;p&gt;What started as an internal AI experiment became a multi-tenant platform supporting around 14 brands.&lt;/p&gt;

&lt;p&gt;The platform could connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Discovery
   ↓
Research
   ↓
AI Agents
   ↓
SEO / AEO / GEO
   ↓
Human Feedback
   ↓
CMS
   ↓
Publishing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important engineering lesson wasn't “use AI agents.”&lt;/p&gt;

&lt;p&gt;It was understanding when a prototype has stopped being a prototype.&lt;/p&gt;

&lt;p&gt;Once an AI workflow becomes part of a real healthcare business, reliability, context, model abstraction, human review and scalable architecture become product requirements.&lt;/p&gt;

&lt;p&gt;That's where the engineering work really begins.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>healthcare</category>
      <category>telehealthcare</category>
    </item>
    <item>
      <title>Designing an EHR Integration Layer for a Telehealth Platform</title>
      <dc:creator>Om Jariwala</dc:creator>
      <pubDate>Mon, 05 Oct 2026 11:51:35 +0000</pubDate>
      <link>https://dev.to/omjariwala/designing-an-ehr-integration-layer-for-a-telehealth-platform-8h5</link>
      <guid>https://dev.to/omjariwala/designing-an-ehr-integration-layer-for-a-telehealth-platform-8h5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fl8p3e50yvl70wigppw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fl8p3e50yvl70wigppw.png" alt=" " width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When developers hear “EHR integration,” the first thought is usually an API.&lt;/p&gt;

&lt;p&gt;But a production telehealth platform needs more than an API client.&lt;/p&gt;

&lt;p&gt;It needs an integration layer that can handle authentication, mapping, retries, errors, synchronization and external-system failures.&lt;/p&gt;

&lt;p&gt;A simplified architecture looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Patient App
     |
     v
Telehealth API
     |
     v
Integration Layer
     |
     +------&amp;gt; EHR
     |
     +------&amp;gt; Lab
     |
     +------&amp;gt; Pharmacy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What should the integration layer handle?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Authentication
&lt;/h3&gt;

&lt;p&gt;The integration layer should manage the authentication mechanism required by the target system.&lt;/p&gt;

&lt;p&gt;Don't spread EHR credentials and integration logic throughout the core application.&lt;/p&gt;

&lt;p&gt;Keep the external integration boundary explicit.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data Mapping
&lt;/h3&gt;

&lt;p&gt;Your internal patient model may not match the external EHR representation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telehealth Patient
    |
    +-- firstName
    +-- lastName
    +-- dateOfBirth
    +-- email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may need to map into a FHIR Patient resource or another format supported by the EHR.&lt;/p&gt;

&lt;p&gt;The mapping should be explicit and testable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Patient Identity
&lt;/h3&gt;

&lt;p&gt;This is one of the hardest parts.&lt;/p&gt;

&lt;p&gt;The same patient can have different identifiers across systems.&lt;/p&gt;

&lt;p&gt;You need a reliable strategy for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existing patient lookup&lt;/li&gt;
&lt;li&gt;Patient creation&lt;/li&gt;
&lt;li&gt;Identifier mapping&lt;/li&gt;
&lt;li&gt;Duplicate prevention&lt;/li&gt;
&lt;li&gt;Reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Retry and Idempotency
&lt;/h3&gt;

&lt;p&gt;Consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telehealth Backend
      |
      | Create Encounter
      v
     EHR
      |
      X Timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the EHR reject the request?&lt;/p&gt;

&lt;p&gt;Or did it process the request but the response never reach your application?&lt;/p&gt;

&lt;p&gt;Retrying blindly could create a duplicate.&lt;/p&gt;

&lt;p&gt;This is where idempotency and transaction tracking matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Failure Handling
&lt;/h3&gt;

&lt;p&gt;External healthcare systems can fail.&lt;/p&gt;

&lt;p&gt;Your integration layer should have a defined strategy for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeouts&lt;/li&gt;
&lt;li&gt;Rate limits&lt;/li&gt;
&lt;li&gt;Authentication failures&lt;/li&gt;
&lt;li&gt;Validation failures&lt;/li&gt;
&lt;li&gt;Temporary outages&lt;/li&gt;
&lt;li&gt;Partial failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Queues can help decouple patient-facing workflows from slower external operations where the workflow allows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read vs Write
&lt;/h2&gt;

&lt;p&gt;A basic integration might only retrieve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EHR → Telehealth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mature workflow may require:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EHR → Telehealth
Telehealth → EHR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Patient
   |
   v
Telehealth consultation
   |
   v
Clinical documentation
   |
   v
Integration Layer
   |
   v
EHR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second direction is often where integration requirements become more complicated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the workflow, not just the endpoint
&lt;/h2&gt;

&lt;p&gt;A successful API response isn't enough.&lt;/p&gt;

&lt;p&gt;Test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existing patients&lt;/li&gt;
&lt;li&gt;New patients&lt;/li&gt;
&lt;li&gt;Duplicate patients&lt;/li&gt;
&lt;li&gt;Failed requests&lt;/li&gt;
&lt;li&gt;Retry scenarios&lt;/li&gt;
&lt;li&gt;Missing data&lt;/li&gt;
&lt;li&gt;EHR downtime&lt;/li&gt;
&lt;li&gt;Duplicate submissions&lt;/li&gt;
&lt;li&gt;Write-back failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An EHR integration is production-ready when the surrounding workflow has predictable behavior, including failure scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The integration layer is not just plumbing between two APIs.&lt;/p&gt;

&lt;p&gt;It is a boundary between two systems with different data models, identifiers, failure modes and workflows.&lt;/p&gt;

&lt;p&gt;Design that boundary carefully, and future EHR integrations become much easier to manage.&lt;/p&gt;

</description>
      <category>ehrintegration</category>
      <category>healthcare</category>
      <category>discuss</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>Why Traditional Webhooks Break Under RPM Load</title>
      <dc:creator>Om Jariwala</dc:creator>
      <pubDate>Fri, 02 Oct 2026 05:32:03 +0000</pubDate>
      <link>https://dev.to/omjariwala/why-traditional-webhooks-break-under-rpm-load-470l</link>
      <guid>https://dev.to/omjariwala/why-traditional-webhooks-break-under-rpm-load-470l</guid>
      <description>&lt;p&gt;A webhook works fine when your system is small.&lt;/p&gt;

&lt;p&gt;But when you start handling &lt;strong&gt;FHIR data, RPM devices, patient observations, alerts, and hundreds of events&lt;/strong&gt;, the simple:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Device → Webhook → API → Database&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;model can become a problem.&lt;/p&gt;

&lt;p&gt;The issue isn't the webhook itself.&lt;/p&gt;

&lt;p&gt;It's making the webhook responsible for everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPM Device
    ↓
Webhook
    ↓
Validate
    ↓
Transform to FHIR
    ↓
Save Data
    ↓
Run Clinical Rules
    ↓
Send Alert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine hundreds of devices doing this at the same time.&lt;/p&gt;

&lt;p&gt;One slow dependency can hold up the entire request.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better approach
&lt;/h2&gt;

&lt;p&gt;Use the webhook for ingestion and move the heavy work into an event-driven pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPM Device
    ↓
API / Webhook
    ↓
Message Queue
    ↓
Workers
    ↓
FHIR Processing
    ↓
Clinical Rules
    ↓
FHIR Server / Database
    ↓
Notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The queue gives your system somewhere to hold events when traffic spikes or a downstream service is temporarily unavailable.&lt;/p&gt;

&lt;p&gt;It also makes retries much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't forget duplicate events
&lt;/h2&gt;

&lt;p&gt;Distributed systems can deliver the same event more than once.&lt;/p&gt;

&lt;p&gt;So your pipeline should be idempotent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receive Event
      ↓
Check Event ID
      ↓
Already Processed?
   ↙          ↘
 Yes           No
 ↓             ↓
Ignore       Process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processing an event twice should not create incorrect clinical data.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What about FHIR?
&lt;/h2&gt;

&lt;p&gt;An RPM reading might become a FHIR &lt;code&gt;Observation&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resourceType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Observation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"final"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"coding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://loinc.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8480-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Systolic blood pressure"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valueQuantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;142&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mmHg"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But creating the FHIR resource is only one part.&lt;/p&gt;

&lt;p&gt;You still need to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Patient and device identity&lt;/li&gt;
&lt;li&gt;Duplicate events&lt;/li&gt;
&lt;li&gt;Out-of-order events&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Clinical rules&lt;/li&gt;
&lt;li&gt;Auditability&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The real question
&lt;/h2&gt;

&lt;p&gt;Don't ask only:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Does my webhook work?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What happens when 10x the events arrive, the FHIR server goes down, or the same event arrives twice?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's where architecture starts to matter.&lt;/p&gt;

&lt;p&gt;A webhook should tell your system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Something happened.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It shouldn't necessarily be responsible for everything that happens next.&lt;/p&gt;

&lt;p&gt;For RPM and FHIR systems, separating &lt;strong&gt;ingestion from processing&lt;/strong&gt; gives you a much stronger foundation for scaling and reliability.&lt;/p&gt;

</description>
      <category>healthcare</category>
      <category>fhirhealthcare</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
