Integration guide
Everything your billing system needs: one endpoint to create a pay-over-time link, one webhook to learn it funded.
Epic (SMART on FHIR) integration
Epic customers can use EMRfi without any API work. EMRfi is a Production Ready SMART on FHIR (R4) app on Epic on FHIR (App ID 61605): staff launch it from a patient's chart, it reads only the patient's name, date of birth, and MRN so they can confirm the right person, and it writes nothing back to Epic. Hand these two documents to your Epic analysts and your security team:
- Epic Build Sheet — client IDs, launch and redirect URLs, scopes, step-by-step build, network notes.
- Security & Privacy Summary — data flow, PHI handling, controls, subprocessors.
Supporting documents: Business Associate Agreement · Security overview · Security questionnaire · Data flow & subprocessors · Incident response · SMART App Launch conformance report (ONC Inferno, passed Nov 3, 2026)
Independent checks (Nov 3, 2026): SSL Labs A+ · Security Headers A+ · ONC Inferno SMART App Launch STU 2.2 Pass. Links and the report are on the Security page.
Authentication
Every request carries your organization's API key, shown once in the provider portal when your account is created.
Authorization: Bearer pcf_your_key Base URL: https://api.emrfi.com/api/partner
Keys are stored hashed on our side; if one is lost, EMRfi issues a new one and retires the old.
Flow
- Your system creates a pay-over-time session for a balance and receives a patient URL.
- You deliver the URL to the patient (SMS, email, patient portal message, or a button in your app).
- The patient sees a page in your branding and chooses Affirm or Sunbit and applies with that lender (about ninety seconds).
- When the payment plan funds, we POST a signed webhook to you and the session status becomes
funded. Money settles to your Stripe account and then your bank on your payout schedule.
Create a pay-over-time session
POST /financing-sessions
Content-Type: application/json
{
"amountCents": 150000, // required, integer, the balance owed to you
"externalRef": "ENC-48213", // required, your invoice / encounter / account id
"patientRef": "MRN-00912", // optional, an identifier meaningful to you (no PHI)
"patientEmail": "pt@example.com", // optional, prefills Affirm's application
"description": "Visit balance", // optional, shown on the patient page (no PHI)
"method": "affirm" // optional: "affirm" is the only supported method
}
Response 201:
{
"id": 42,
"url": "https://emrfi.com/pay/3f9c…", // give this to the patient
"status": "created",
"method": "affirm",
"balanceCents": 150000,
"platformFeeCents": 4350,
"stripeFeeCents": 9884,
"patientTotalCents": 164234,
"providerNetCents": 150000,
"expiresAt": "2026-09-21T14:03:11.000Z" // links expire after 24 hours; create a new one if needed
}
Affirm requires a balance of at least $50.00. Below that, or for organizations without a lender active, the API returns an error and the patient can pay the provider directly.
Check status
GET /financing-sessions/{id}
GET /financing-sessions // most recent 200
| status | meaning |
|---|---|
created | Link issued, patient has not completed. |
funded | Payment plan approved and paid. Post the payment to the account. |
expired | Link expired unused. Create a new session. |
cancelled | Voided before funding, or reversed after funding. |
Void or reverse
POST /financing-sessions/{id}/cancel
On a created session this voids the link. On a funded session it issues a full refund, including all fees, and the lender cancels the patient's payment plan. Use it when a balance was posted in error or is later covered by insurance.
Webhooks
Set your webhook URL in the portal (Integration tab) or with PATCH /account. We POST JSON with a signature header:
POST https://your-system.example.com/emrfi
pcf-signature: t=1758380001,v1=9b3d…
{ "type": "financing_session.funded",
"createdAt": "2026-09-20T14:53:21.000Z",
"data": { "sessionId": 42, "externalRef": "ENC-48213", "patientRef": "MRN-00912",
"balanceCents": 150000, "amountCents": 164234, "platformFeeCents": 4350,
"paymentIntent": "pi_3…" } }
Verify it: compute HMAC-SHA256 over t + "." + rawBody with your webhook secret, compare to v1, and reject timestamps older than five minutes.
// Node
const [t, v1] = sig.split(',').map(x => x.split('=')[1]);
const expect = crypto.createHmac('sha256', SECRET).update(`${t}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expect), Buffer.from(v1)) && Date.now()/1000 - t < 300;
Respond with any 2xx. We attempt delivery once and mark the session notified; poll GET /financing-sessions/{id} as a backstop.
Quotes
GET /quote?amountCents=150000&method=affirm
Returns the same fee fields as a session, without creating one. Useful for showing a patient "pay $1,642.34 over time" before they click.
Account
GET /account // status, branding, fee model
PATCH /account // { brandName, logoUrl, brandColor, supportPhone, webhookUrl }
POST /account/sync // refresh payment status from Stripe
POST /onboarding-link // hosted Stripe verification (the portal embeds this for you)
Errors
| code | when |
|---|---|
| 400 | Missing or invalid field; message says which. |
| 401 | Missing or unknown API key. |
| 404 | Session not found (or belongs to another organization). |
| 422 | Action not allowed in this state, e.g. payments setup incomplete, Affirm not active, balance below minimum. |
PHI: two paths
API integrations: send us an amount and identifiers that mean something to you. Do not put names, dates of birth, diagnoses, or clinical detail in externalRef, patientRef, or description; the API is designed to work without PHI. Epic launch: EMRfi reads the patient's name, date of birth, and MRN from Epic so staff can confirm the right patient, and nothing else; this is covered by the Business Associate Agreement your organization signs at onboarding. In both paths the patient's payment plan application is completed with the lender (Affirm or Sunbit) under the lender's own privacy notice; application data never passes through EMRfi.