eClinicalWorks Integration
The eClinicalWorks integration is a read-only FHIR R4 Bulk Data connection. It authenticates with SMART Backend Services (asymmetric RS384 private_key_jwt) and pulls one Dataset per clinical FHIR resource. Clinical Datasets use $export. There is no user login.
The eClinicalWorks integration does not run through the Resplendent Sync Agent.
Before you begin
Section titled “Before you begin”You need:
- A practice FHIR R4 base URL (the SMART issuer), such as the eCW sandbox
https://staging-fhir.ecwcloud.com/fhir/r4/FFBJCD. - A FHIR
Groupid created in the practice’s EMR Registry and enabled for the Connect app. eClinicalWorks does not supportPatient/$exportor server-level$exportfor this integration.
Resplendent owns one published Backend / Bulk API Connect app for all customer connections. Its client id (ECW_CLIENT_ID), JWKS key id (ECW_JWKS_KID), and RSA private key (ECW_JWT_PRIVATE_KEY) are backend configuration and are never entered or stored on a customer connection. The private key must never be pasted into Resplendent, tickets, or git. The public key is hosted at https://app.resplendentdata.com/api/.well-known/jwks.json.
eClinicalWorks assigns different client ids to the sandbox and production configurations of the same published app. Set ECW_CLIENT_ID from the configuration that matches the FHIR environment; a sandbox client id cannot authenticate against a production issuer.
The practice must activate its eClinicalWorks FHIR APIs and separately authorize the Group for Resplendent’s Connect app. In eCW this is under Admin > Product Activation > FHIR APIs > Bulk/Backend Apps > Manage Groups; move the cohort into Enabled Groups. App publication, customer activation, and successful token issuance do not grant Group access by themselves. The shared eCW sandbox also needs an app-enabled sandbox Group before it can return synthetic patient data.
The token URL is discovered from {issuer}/metadata?_format=json (oauth-uris.token). Do not hardcode a practice token host.
The integration never creates, updates, or deletes records in eClinicalWorks.
Connect eClinicalWorks to Resplendent Data
Section titled “Connect eClinicalWorks to Resplendent Data”- In Resplendent Data, go to Settings → Integrations.
- Find eClinicalWorks and select Connect.
- Enter:
- FHIR base URL: the practice issuer.
- Group ID: the FHIR Group that the practice enabled for this app.
- Test and save the connection.
- Select the Datasets and columns you want to sync.
The connection test discovers the token URL and mints an RS384 client assertion (iss/sub = the backend client id, aud = token URL, kid in the header). eClinicalWorks rejects GET /Group for backend tokens even when the practice has enabled the Group, so Resplendent verifies the real authorization boundary by starting Group/{id}/$export for Patient with _since set to the current time and immediately cancelling the job. It never downloads the export files. An “already in progress” response also proves that eCW accepted the token, Group, and export route. If eClinicalWorks rejects the token request, Resplendent shows its safe OAuth error fields without exposing the assertion or private key.
Token requests use least privilege. The connection test requests system/Group.read and system/Patient.read; each clinical Dataset requests system/Group.read plus that Dataset’s system/{Resource}.read scope.
Local and sandbox development should use the eCW FHIR sandbox issuer above. Do not point exploratory syncs at a customer production practice.
Datasets
Section titled “Datasets”Resplendent syncs typed Datasets rather than one giant $export dump. Each clinical Dataset kicks Group/{id}/$export with _type set to that resource so a 60-second job never drains the whole server.
| Dataset | FHIR resource | Sync | Why |
|---|---|---|---|
| Patients | Patient |
Incremental | meta.lastUpdated exists, but Bulk files have no stable row watermark across NDJSON files. History is a JSON cursor over export files. |
| Encounters | Encounter |
Incremental | Same Bulk file cursor. |
| Observations | Observation |
Incremental | Same Bulk file cursor. Often the largest file set. |
| Conditions | Condition |
Incremental | Same Bulk file cursor. |
| Procedures | Procedure |
Incremental | Same Bulk file cursor. |
| Medication Requests | MedicationRequest |
Incremental | Same Bulk file cursor. |
| Allergy Intolerances | AllergyIntolerance |
Incremental | Same Bulk file cursor. |
| Immunizations | Immunization |
Incremental | Same Bulk file cursor. |
| Diagnostic Reports | DiagnosticReport |
Incremental | Same Bulk file cursor. |
| Coverages | Coverage |
Incremental | Same Bulk file cursor. |
| Care Plans | CarePlan |
Incremental | Same Bulk file cursor. |
| Care Teams | CareTeam |
Incremental | Same Bulk file cursor. |
| Goals | Goal |
Incremental | Same Bulk file cursor. |
| Devices | Device |
Incremental | Same Bulk file cursor. |
| Practitioners | Practitioner |
Incremental | Same Bulk file cursor. |
| Practitioner Roles | PractitionerRole |
Incremental | Same Bulk file cursor. |
| Organizations | Organization |
Incremental | Same Bulk file cursor. |
| Locations | Location |
Incremental | Same Bulk file cursor. |
Sync behavior
Section titled “Sync behavior”- Clinical Datasets are incremental only in the crawl sense:
initial_pullreturns one configured batch (batch_pull_size, default 1000) from the current$exportand stores the status URL plus NDJSON file/line offsets oncrawler_step_info.get_old_rowscontinues that cursor. New Datasets default to 9999 additional history batches so a large export can finish. This is the same JSON-cursor style as HubSpot, not a third pager. - A full
$exportis not drained insideinitial_pull. If the export is still assembling, the job retries on the next tick using the saved status URL. If eCW returns HTTP 200 OperationOutcomeduplicate(“Bulk operation is already in progress for the group”) with no Content-Location, that is the same pending state: the job does not write an empty page or mark the crawler completed, and it retries the same Group$exporton the next tick (or polls if a status URL exists). get_updated_rowsuses Bulk_sinceagainstmeta.lastUpdated(with a short overlap) once history is no longer mid-cursor._sincedoes not emit deletes.nightly_resyncis on so hard deletes and missed Bulk files disappear after a wipe-and-recrawl. Resync still first-pages incremental Datasets;last_resync_timestampis not a fetch-everything switch.
Nested FHIR elements are stored as JSON strings. Identity columns are id and lastUpdated.
Related links
Section titled “Related links”- SMART Backend Services
- FHIR Bulk Data Export
- Public JWKS:
https://app.resplendentdata.com/api/.well-known/jwks.json