Skip to content

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.

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 Group id created in the practice’s EMR Registry and enabled for the Connect app. eClinicalWorks does not support Patient/$export or server-level $export for 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”
  1. In Resplendent Data, go to Settings → Integrations.
  2. Find eClinicalWorks and select Connect.
  3. Enter:
    • FHIR base URL: the practice issuer.
    • Group ID: the FHIR Group that the practice enabled for this app.
  4. Test and save the connection.
  5. 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.

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.
  • Clinical Datasets are incremental only in the crawl sense: initial_pull returns one configured batch (batch_pull_size, default 1000) from the current $export and stores the status URL plus NDJSON file/line offsets on crawler_step_info. get_old_rows continues 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 $export is not drained inside initial_pull. If the export is still assembling, the job retries on the next tick using the saved status URL. If eCW returns HTTP 200 OperationOutcome duplicate (“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 $export on the next tick (or polls if a status URL exists).
  • get_updated_rows uses Bulk _since against meta.lastUpdated (with a short overlap) once history is no longer mid-cursor. _since does not emit deletes.
  • nightly_resync is on so hard deletes and missed Bulk files disappear after a wipe-and-recrawl. Resync still first-pages incremental Datasets; last_resync_timestamp is not a fetch-everything switch.

Nested FHIR elements are stored as JSON strings. Identity columns are id and lastUpdated.