Backend EHR Integrations
This guide explains how to integrate Descope with Epic, Meditech, OpenEMR, and other EHR systems.
This integration allows your backend service to securely authenticate with an EHR system using a signed JWT (client assertion) instead of a client secret. This is the standard backend-only / system-level SMART on FHIR flow, designed for cases where no end-user is present.
By handling JWT construction, signing, and EHR-specific token endpoint requirements, Descope removes the complexity typically involved in connecting to EHR platforms such as Epic, Meditech, OpenEMR, and other SMART on FHIR-compliant systems.
Overview
SMART Backend Services enable backend applications to authenticate to EHR systems without user interaction. Instead of using traditional client secrets, your service generates a signed JWT assertion using a private key.
The EHR validates this assertion using your public key and issues an access token. This approach allows you to request system-level scopes like system/*.read and access FHIR resources from automated server processes, scheduled jobs, or background services.
Descope supports both directions of this flow:
Outbound Flow
Descope generates signed JWT assertions and obtains EHR-issued access tokens that your backend can use to call FHIR APIs.
Inbound Flow
Descope can accept EHR-issued JWTs or access tokens and exchange them for Descope tokens. This enables you to:
- Treat an EHR-issued token as an authenticated identity,
- Map EHR users or service accounts into Descope identities,
- Normalize and unify identity handling across multiple EHR systems.
Outbound Flow (Descope → EHR)
This flow lets your backend obtain an EHR access token to call FHIR APIs.
- Register your backend app in the EHR.
- Upload your Descope public key (JWKs) to the EHR.
- Use Descope to generate a client assertion JWT.
- Send the JWT to the EHR token endpoint with
grant_type=jwt-bearer. - The EHR validates the assertion and returns an access token.
- Use the access token to call FHIR endpoints.
1. Register Your Backend App in the EHR
Epic
- Log into Epic App Orchard / Connection Hub.
- Register a Backend Service / System App.
- Select required scopes (for example,
system/*.read). - Note the Client ID and Token URL.
- Upload your Descope Public Key (JWKs).
Other Supported EHR Systems
Descope supports integration with these EHR systems out of the box:
| EHR | Token Endpoint | Algorithm | Notes |
|---|---|---|---|
| Meditech | https://<host>/token | RS256 | Standard SMART backend services |
| Medplum | https://<host>/oauth2/token | RS256 | Ideal for testing & development |
| eClinicalWorks (eCW) | https://<host>/oauth2/token | RS256 | Standard OAuth2 implementation |
| OpenEMR | https://<host>/oauth2/default/token | RS384 | Requires RS384 signing algorithm |
Descope automatically handles the correct signing algorithm, JWT claim structure, and token exchange format for each EHR system. For any SMART on FHIR-compliant EHR not listed here, Descope can be configured with custom token endpoints and signing algorithms.
2. Get Your Descope Public Key
Get your Descope public key from the project-level JWKs URL:
Note
Replace api.descope.com in the URL with your custom domain if applicable.
Descope automatically manages the private key used for signing. The JWKs endpoint contains the public key that EHR systems need to validate your client assertions. Upload this public key (or provide the JWKs URL) to your EHR system during app registration.
3. Generate a Client Assertion JWT
Note
JWT templates configured in your Descope Project do not apply to this client assertion JWT.
You have two options for generating the client assertion:
Note
The flattenAudience parameter is optional and will ensure the aud claim is a single string rather than an array.
Note
It is recommended to use a short lifetime in the expiresIn parameter for client assertions (for example, 300 seconds) to ensure the client assertion is not reused.
4. Exchange the Assertion for an EHR Access Token
Once you have the client assertion JWT from Descope, exchange it with the EHR:
Example Epic response:
5. Use the Access Token to Call FHIR
Use the returned EHR access token in the Authorization header:
You can now perform FHIR operations allowed by the granted scopes (for example, system/*.read).
Example: End-to-End Outbound Flow
Multi-EHR / Multi-Tenant Patterns
For Descope projects that involve multiple tenants integrating with different EHR vendors:
- Maintain a configuration map per tenant or per EHR. You can use tenant custom attributes to store the configuration, including:
- EHR type (Epic, Meditech, OpenEMR, etc.)
- Client ID
- Token URL
- FHIR base URL
Example config shape:
Your backend can then look up the tenant attribute based on the user who is logged in, and then use the respective values to generate a client assertion JWT, as described above.
Inbound Flow (External JWT → Descope Token)
Note
This Inbound Flow requires a specific license. Please contact Descope Support to enable this feature for your project.
In the inbound flow, you receive a token from the EHR (or another SMART-compliant identity provider) and want to:
- Accept it as user identity.
- Map the subject to a Descope user.
- Issue Descope tokens (session, access, refresh).
- Normalize identity across multiple EHRs.
1. Configure External Token Validation
You can configure external token validation under the External Token Validation section of your Inbound App settings. If you do not already have an inbound application, you must create one first.
![]()
Issuer URL
Enter the issuer URL for your EHR system. Some examples are:
- Epic:
https://fhir.epic.com/interconnect-fhir-oauth/oauth2 - Meditech:
https://<your-meditech-host>/oauth2 - OpenEMR:
https://<your-openemr-host>/oauth2/default
JWKs URL
You can manually override the JWKs URL if your EHR uses custom endpoints or if automatic discovery is not available.
Signing Algorithm
Select the signing algorithm used by your EHR system. The currently supported algorithms are:
- RS256
- ES384
- ES256
- ES512
User Information Endpoint URL
Optionally configure a /me or userinfo endpoint to pull additional user metadata after validating the JWT.
User Information LoginID Field Name
Choose which JWT claim identifies the user in Descope. Some examples are:
subfhirUser- Any nested claim (for example,
claims.user.id) - A field from the
/meresponse
Note
Currently, only the user identifier is supported. Other user attributes/information from the token cannot be mapped to user attributes in Descope.
2. Exchange the External Token with Descope
Note
EHR access tokens are typically short-lived tokens (5 - 15 minutes), and therefore must be exchanged quickly after generation.
To exchange the EHR-issued token for a Descope token:
Descope validates the external token and returns:
You can now use access_token as a standard Descope token in your APIs and frontends.
Troubleshooting
These are some common errors you might encounter when integrating with EHR systems:
| Symptom / Error | Likely Cause | How to Fix |
|---|---|---|
invalid_client | issuer / subject don't match the registered client ID | Ensure both issuer and subject match the EHR client ID exactly. |
invalid_grant or invalid_request | audience does not match token URL; or assertion expired | Use the exact token endpoint URL as audience; ensure expiresIn is valid. |
| Signature verification failed | Using the wrong algorithm or stale public key | Confirm RS256 vs RS384; re-upload JWKs; ensure you're using the right key. |
| 401/403 when calling FHIR endpoints | Missing or insufficient scopes | Confirm the registered app has system/*.read or required resource scopes. |
| Works in one environment but not another (sandbox vs prod) | Different token URLs, issuers, or keys per environment | Update config for each environment and ensure correct issuer/audience. |
Testing & Sandbox Guidance
For development and testing:
- Use EHR sandbox environments (Epic Sandbox, Medplum, etc.).
- Start with read-only scopes such as
system/*.read. - Create a simple health check in your backend that:
- Calls Descope to generate a client assertion.
- Exchanges it with the EHR for an access token.
- Calls a lightweight endpoint (for example,
/metadataor/Patient). - Returns success/failure and any relevant error messages.
This gives you a single endpoint to verify that Descope configuration and EHR configuration are all working end-to-end.
Fingerprinting
This guide explains the fingerprinting capabilities available in Descope, including device fingerprinting, risk-based authentication, and bot detection.
SMART on FHIR
How to integrate Descope as an OAuth provider for SMART on FHIR applications, supporting both EHR Launch and Standalone Launch flows.