Behavioral health integration fit check
30 minutes · Please do not submit PHI
Choose the smallest integration that removes the handoff.
Bring your EHR, Salesforce edition, and the step your staff repeats today. Leave with the recommended shape, known API or access blockers, and a practical next step.
Admission push
Create the patient, episode, and packet when the admission milestone is reached.
Real-time syncThis build
Keep patient and appointment changes aligned while both teams stay in their system.
Operating layer
Add census, VOB, documents, and attribution around the clinical system.
A multi-state psychiatric health practice came to us running intake twice: once in Salesforce, where admissions and scheduling worked, and once in AdvancedMD, their practice-management EHR, where clinical and billing lived. Two teams keyed the same patient. Demographics drifted within weeks. Nobody could say which system was right, so staff checked both, which is how a "time-saving" CRM quietly adds headcount.
What they have now: a patient entered in either system appears in the other within seconds, appointments included, with guards that stop a duplicate chart before it exists. Intake staff enter someone once. Nobody checks both systems anymore.
Below is how it actually works, because the interesting part is not that we synced two systems. It is the failure modes we had to design around to make it hold up in production, hardened across sixteen releases.
Vendor access is the first gating assumption. AdvancedMD's interoperability guide says its FHIR APIs are read-only and that transactional work uses its Connect APIs, which require a developer agreement, associated fees, sandbox testing, and production approval.
Why EHR-CRM sync usually fails
The naive integration is easy to draw: the EHR fires a webhook, Salesforce catches it in an Apex REST endpoint, the endpoint updates the patient record. It works in the demo. Then it meets production traffic, and three things happen in sequence.
First, webhooks arrive in bursts (a morning schedule import, a batch eligibility run, a clinic opening for the day), and each synchronous handler now does DML while the platform counts every statement against governor limits. Second, those writes fire the org's existing triggers and record-triggered flows, so an innocent demographics update cascades into automation the integration author has never read, and two near-simultaneous webhooks for the same patient collide in a row lock. Third, the caller can time out before it receives the response even after Salesforce commits the transaction. A webhook retry can then repeat the work unless identity and re-entry are guarded; suppressed errors or an unmonitored asynchronous handoff can instead let the systems drift. Drift is the failure mode that erodes trust: not the outage everyone notices, but the phone number that's right in one system and stale in the other.
The decoupling move: the handler does one job
The fix is an old integration principle applied with Salesforce-native parts: separate receiving the event from acting on it. Inbound, the REST handler does exactly one thing: validate the payload and publish a Platform Event. No DML, no queries against business objects, no trigger cascade. It returns in milliseconds in this build, which helps keep processing inside the EHR's webhook timeout and leaves materially less synchronous work that can fail.
Subscriber Flows do the actual writes asynchronously on the other side of the event bus. The bus decouples receipt from processing, and retained high-volume events can be replayed within Salesforce's documented window when a subscriber needs to catch up. Replay is a recovery tool, not a guarantee that removes the need for monitoring and reconciliation. Salesforce's integration-pattern guidance makes the same distinction and identifies when true queueing, choreography, or quality of service still calls for middleware. In this build the handlers are deliberately small (the demographics handler is 150 lines, the appointments handler 149, each with its own test class) because a handler that only validates and publishes has less surface area to break.
| Layer | Component | Job |
|---|---|---|
| Inbound edge | Apex REST handlers (150 / 149 LOC) | Validate the webhook, publish the event, return fast |
| Buffer | Platform Events (3 in the build) | Absorb bursts, decouple receipt from processing, replay |
| Writes | Subscriber Flows (24 flows in the project) | Upsert Person Accounts, update Opportunities, branch on event type |
| Outbound | Account trigger + invocable Apex | Push new patients/providers to the EHR, write back the MRN |
| Config | Custom Metadata field mappings | Let admins remap EHR fields without a deployment |
| Observability | Dedicated error-log object | Make every failure visible to a human |
Inbound: a 38-field patient event
The core of the inbound design is Patient_Event__e, a Platform Event carrying 38 fields of patient demographics: names, contact details, addresses, insurance identifiers, status flags. That width is a deliberate choice. A skinny event ("patient 4127 changed, go fetch") forces every subscriber to call back into the EHR, which reintroduces callout limits, latency, and a second failure mode exactly where you removed the first one. A fat event makes each message self-contained: the subscriber Flow has everything it needs to upsert the Person Account without ever leaving Salesforce.
Person Accounts matter here. Healthcare patients are people, not companies with contacts, and modeling them as Person Accounts keeps one record per human across scheduling, billing conversations, and care history. The subscriber Flow matches on the EHR's patient identifier, updates the existing Person Account if one exists, and creates it if not, idempotently, because webhooks get redelivered and a duplicated patient is not a cosmetic bug in a psychiatric practice.
Appointments travel on their own event, Appointments_Events__e, and land on Opportunities, which sounds odd until you remember what a CRM is for. In this practice, an appointment is the revenue moment: scheduled, confirmed, completed, no-showed. Flowing appointment status onto Opportunities gave operations a real pipeline view of care delivery without anyone re-keying a schedule.
Outbound: MRN writeback closes the loop
Real-time inbound sync is only half a bidirectional integration. When the practice's own team creates a patient in Salesforce (a new intake that starts with a phone call, not an EHR record), an Account trigger hands off to invocable Apex that pushes the patient (and, through a parallel path, new providers) to AdvancedMD over its REST API.
The detail that makes this reliable is the writeback: the EHR assigns the medical record number, and the integration immediately writes that MRN onto the Salesforce record. From that moment both systems agree on identity, every future inbound event matches deterministically, and the risk of a duplicate patient from an unmatched retry is substantially reduced. Every outbound callout failure writes to a dedicated error-log object (payload metadata, status code, and timestamp, with the log design excluding clinical narrative), because in healthcare the second-worst outcome is failure, and the worst is silent failure.
Config over code: the Custom Metadata mapping engine
EHR schemas move. Payers demand new fields; practices customize forms; vendors rename things in API updates. If your field mappings are hardcoded in Apex, every one of those changes is a development ticket, a code review, and a deployment window.
In this build the mappings live in Custom Metadata records: EHR field on one side, Salesforce field API name on the other, with the transformation logic reading the mapping table at runtime. When AdvancedMD adds a compatible field, an admin can create one metadata record and let the existing mapping engine carry it. Changes to the API contract or transformation logic still receive engineering review. Salesforce also documents static access to Custom Metadata without the SOQL engine. That configuration decision changed the operating economics of the integration: the client's own admin handles routine mapping drift, and we get called for architecture rather than column renames. It's the same principle we apply across integration engineering engagements: code for behavior, configuration for facts.
AdvancedMD integration fit check
Map the safest path between AdvancedMD and Salesforce.
The PHI posture
A patient sync moves protected health information by definition, so the guardrails are part of the architecture, not an addendum. Event payloads are scoped against HHS's minimum-necessary guidance. The error-log object stores what operations needs to diagnose a failure while excluding clinical narrative by design. As a companion project, we rolled out Shield Platform Encryption over the PHI fields on Opportunities, so clinical context is encrypted at rest without breaking the automation that reads it.
And we operate as a HIPAA-compliant organization, building this sync to HIPAA-compliant standards under a BAA so the integration is never the weak link. The practice's overall compliance program (their BAA, their policies, their training) stays theirs to maintain and certify, which is exactly how it should be.
Hardened, not launched: 16 production releases
The repository for this build contains 16 dated pre-deployment snapshots. That's not an ornament; it's the honest fingerprint of what real-time healthcare integration takes. Webhook payloads showed up with fields the documentation didn't mention. Event subscribers needed reordering once real volume exposed a race. The mapping engine grew branches for edge cases no one listed in discovery. Each snapshot marks a production release where the system got more boring, which is the goal. Anyone selling you a one-shot EHR integration is describing the demo, not the system. This build is one of four behavioral-health platforms we've written up in Salesforce for behavioral health, and the pattern (events as shock absorbers, config over code, error logs as first-class objects) repeats across all of them.
Scoping your own EHR integration: the checklist
- Inventory the events, not the fields. What does the EHR emit (patient created, demographics changed, appointment status)? The event list defines the integration; fields are just payload.
- Decide the system of record per attribute. Demographics from the EHR, engagement from the CRM, identity via MRN writeback. Undecided ownership is how drift starts.
- Demand idempotency everywhere. Webhooks redeliver and callouts retry. Every write must be safe to run twice.
- Buffer inbound traffic through Platform Events. When the event volume and recovery requirements fit, keep DML, triggers, and downstream automation out of the webhook handler's execution context.
- Put mappings in Custom Metadata. Schema drift is certain; deployments for column renames are optional.
- Fund the error path. A dedicated log object, an alert rule, and a human who owns the queue. In healthcare, silent failure is the expensive kind.
If the integration also needs to trigger downstream work (census recalculation, document generation, follow-up cadences), that's the adjacent discipline of Salesforce automation, and the event-driven spine above is exactly what makes it safe to build on. The broader context for healthcare operators is on our Salesforce for healthcare page.
Frequently asked questions
Can Salesforce integrate with AdvancedMD?
Yes. AdvancedMD offers proprietary Connect APIs for transactional create, read, update, and delete work, while its FHIR APIs are read-only. In this production build, patient and appointment events land on an Apex REST endpoint that publishes Platform Events inbound, and invocable Apex pushes new patients and referral providers outbound. MRN writeback and re-entry guards reduce duplicate-chart risk. The same pattern can be evaluated for other EHRs that provide the required events and writable API access.
Do we need middleware like MuleSoft or Boomi for an EHR-Salesforce integration?
Sometimes. For a bounded point-to-point sync, Platform Events can provide asynchronous processing and replay within Salesforce. Middleware starts earning its cost when the design requires durable cross-system queuing, quality-of-service guarantees, complex transformations, orchestration, protocol conversion, or fan-out to many systems. We select against those requirements rather than treating either approach as the default.
Why publish a Platform Event instead of writing records directly from the webhook handler?
Because webhooks arrive in bursts and synchronous handlers doing DML inherit every trigger, flow, and governor limit on the objects they touch. A handler that only validates and publishes reduces synchronous work and narrows the failure surface. Subscriber Flows then do the writes asynchronously; monitoring, replay or retry, and reconciliation still have to be designed explicitly.
Is this kind of EHR sync HIPAA compliant?
We are a HIPAA-compliant organization and we build this kind of EHR sync to HIPAA-compliant standards under a BAA: minimum-necessary payloads, Shield Platform Encryption on PHI fields, error logs that never store clinical narrative, and test classes on every code path that touches patient data. Your overall compliance program (your BAA with Salesforce, your policies, your training) stays yours to maintain and certify, and we make sure the integration is never the weak link.
What happens when the EHR adds or renames a field?
Mapping-only changes can be handled without changing Apex. Field mappings live in Custom Metadata records (EHR field name on one side, Salesforce field API name on the other), so an admin can add or edit a compatible mapping. A changed API contract, new transformation, or different workflow still receives engineering review.
Bring us the two systems that don't agree
If your staff keys patients into two systems, or your CRM and EHR each hold a different version of the truth, bring both to a working session. We'll map the event flows, vendor-access constraints, failure path, and the smallest architecture that fits.