Every clinic and medspa runs on two very different payment rails. Insurance claims take weeks to adjudicate, arrive at contracted rates, and require follow-up when they deny or underpay. Cash-pay services collect at time of service, at the price you set, with no middleman. The revenue from each rail behaves differently, plans differently, and even feels different in your bank account.
What surprises most practice owners is that they don't actually know their split — not precisely, and not in real time. They have a rough sense ("we're about 60% insurance"), but when asked whether that number moved last month, or which services are driving it, the answer is usually a shrug and a request to pull the month-end billing report.
We fixed this by building a tracker that reads DrChrono's billing and claims data continuously, classifies every encounter as cash-pay or insurance-covered, and keeps a running split that's always current. Here's how it works and what it's good for.
Why the Split Matters More Than You Think
Before getting into the mechanics, it's worth being precise about what you're actually tracking.
The cash-pay vs. insurance split isn't just an accounting category — it's a leading indicator for several things that matter operationally:
- Cash flow predictability. Cash-pay revenue is in your account same day or next day. Insurance revenue might be 15–90 days out, at an amount that differs from what you expected. A shift in your mix toward insurance means your near-term liquidity shrinks even when volume holds.
- Margin by service line. Many practices discover, when they calculate it, that their cash-pay services have higher net margins than their insurance lines — especially after factoring in the staff time for claims management, appeals, and follow-up calls. The split tells you where your effort is going.
- Exposure to payer changes. A single payer reducing reimbursement rates can swing your revenue meaningfully if you're concentrated there. Knowing your split by payer — not just by cash/insurance — gives you early warning when one carrier starts to dominate.
None of this requires a sophisticated analytics platform. It requires clean data pulled from the right place.
Where the Data Lives in DrChrono
DrChrono stores what you need across two related resources: appointments and their associated billing profiles, and claims.
When a billing code is attached to an appointment, DrChrono creates a claim record. That claim record has a payer attached to it — either a commercial insurance plan, Medicare/Medicaid, or nothing (which indicates cash pay or self-pay). The claim also carries status, submitted amount, and paid amount as it moves through adjudication.
The fields we rely on most from the /api/appointments and /api/billing_profiles endpoints:
billing_statuson the appointment — whether a claim has been submitted, is in progress, or is completeprimary_insurance_idon the appointment — if present, this is an insurance visit; if null, it's cash payprocedure_codes— the CPT codes billed, which we use to classify by service typeclaimobject when resolved — containsamount_paid,payer, anddate_paid
The simplest classification rule: if primary_insurance_id is null and no claim was submitted to a third-party payer, it's cash pay. If a claim exists and routes to a carrier, it's insurance. That rule handles 95% of encounters correctly without any custom logic.
For practices that collect co-pays or have high-deductible patients who pay out of pocket on insured visits, we add a secondary layer that looks at the payment method recorded in DrChrono's line_items — but for most clinics, the simple rule above is sufficient to start.
Building the Classifier
The tracker is a small service that runs nightly — or on-demand when you open the dashboard — and does three things:
- Pull encounters from the past rolling window (typically 90 days, configurable). We use the
/api/appointmentsendpoint filtered by date range andbilling_statusnot equal to "Not Submitted."
- Classify each encounter as cash or insurance. Check
primary_insurance_id: null means cash pay. Non-null means an insurance payer is involved. We also cache the payer name from a separate payer lookup so the split can be broken out by carrier.
- Aggregate into the split report. Sum billed amounts by category, sum paid amounts where claims have settled, and calculate collection rate per category. These three numbers — billed, collected, collection rate — tell most of the story.
The output lands in a simple table we query for the dashboard:
- Today's split (encounters this calendar day, by count and billed amount)
- This week vs. last week
- Rolling 30-day and 90-day averages
- Split by provider and by service type (using CPT code categories)
The nightly sync keeps historical data local so the dashboard loads instantly without waiting on the DrChrono API at read time.
What the Numbers Reveal
Once the tracker has been running for a few months, the patterns that emerge are consistent enough to inform decisions.
Most clinics find one or two service lines that are pulling heavily toward insurance reimbursement at lower collected rates. Sometimes it's worth knowing — you're choosing to offer those services for reasons beyond margin. Sometimes it's a surprise. Either way, you're now choosing consciously rather than discovering the problem during year-end review.
The split also tends to move when you don't expect it to. Medspa services added to a medical clinic shift the mix toward cash pay. A new provider who's in-network with a carrier you weren't previously billing can shift it the other way. The tracker catches these movements in the first billing cycle rather than three months later.
A few things we watch specifically:
- The lag between billed date and paid date by payer. If one carrier's average days-to-payment increases from 25 to 45 days over a quarter, that's a cash flow warning before you feel it in collections.
- Denial rate by CPT code. Not purely a cash/insurance split metric, but useful to surface alongside it — certain codes deny at higher rates with certain payers, and the pattern is only visible when you're already pulling claims data.
- Monthly shift in cash-pay percentage. A steady drift toward more insurance without a deliberate reason is usually a staffing or marketing story worth investigating.
What You Need to Set It Up
The integration sits on top of the DrChrono API with read scope on appointments, billing profiles, and claims. Write access isn't required for the tracker itself — you're reading, classifying, and aggregating, not changing anything in the EHR.
The infrastructure is minimal: a scheduled job, a small relational table to store classified encounters and their amounts, and a query layer for the dashboard. We typically connect this to the same daily revenue dashboard we run for billing — the cash/insurance split is a natural companion metric to daily deposit amounts and open AR.
The main complexity isn't technical — it's the classification edge cases. Encounters with multiple payers (primary and secondary insurance), split-billing scenarios where the patient pays a portion and insurance covers the rest, and voided claims that need to be excluded from rolling averages. None of these are hard to handle, but you need to see a few weeks of real data to know which edge cases your practice actually produces.
The Bigger Picture
Your revenue mix is a strategic number. If you're building out a new service line, evaluating a provider contract, or deciding whether to add an in-network payer, the cash/insurance split by service type is the number you want in front of you before you decide — not after.
We build this kind of reporting as part of our broader DrChrono integration work: pulling the data your EHR holds and surfacing it in a format you can actually act on without a billing degree. The data is already in DrChrono. Getting it into a useful view is usually a matter of days, not months.
If you want to see what your current split looks like — or you're already pulling billing data and want to add this layer — reach out. We'll take a look at your setup and tell you what it would take to have the dashboard running.