Home / Dental Practices / Insurance verification prep 72 hours ahead: eligibility check and front-desk checklist (Open Dental + Stedi)

Dental Practices problem 5: Insurance is verified by phone, at the last minute

Insurance verification prep 72 hours ahead: eligibility check and front-desk checklist (Open Dental + Stedi)

Verified with mocksLast tested 2026-10-28 · pricing checked October 2026

Each weekday at 7 am the workflow takes every appointment three days out, follows the patient's insurance in Open Dental (plan, subscriber, carrier), runs a real-time eligibility check through Stedi and writes a checklist for the front desk: active or not, deductible and annual maximum left, coinsurance, and what to do next. Active coverage is marked verified in Open Dental with a note. Inactive plans, payer errors, low remaining maximums and patients without insurance are flagged for a person. Patients get no text.

The problem

Manual verification takes staff time on every visit, and unverified coverage turns into surprise bills and write-offs. The figures below come from an industry index as reported by a vendor.

Who it's for

US dental offices on Open Dental that verify insurance by phone or portal and want the routine checks done before the day they are needed.

Tools and monthly cost

ToolOfficial price
Stedi eligibility checks (270/271)Sandbox free. Pay as you go: $0.30 per check for the first 250 a month, $0.15 for 251-3,500; $25 starting prepaid balance; no monthly minimum. PHI needs a Stedi HIPAA account and its BAA pricing page
Open Dental API (per location, paid by whoever holds the developer key)Free: Read All only (1 request per 5 s). $15 / month: adds InsuranceSimple (InsVerifies PUT) and Comm (commlogs), which this recipe uses pricing page
Google Workspace with the HIPAA BAA accepted (call lists and logs live in Google Sheets)Any paid Workspace edition (not the legacy free edition) can accept Google's BAA in the Admin console; Sheets is covered. You probably already pay for Workspace pricing page
n8n, self-hostedCommunity Edition is free; you pay for the server. n8n Cloud is not an option here: n8n staff said in January 2026 that n8n does not offer a BAA for Cloud pricing page

Example month: Illustrative arithmetic. Assume 400 insured visits a month: 400 eligibility checks = 250 x $0.30 + 150 x $0.15 = $97.50 at Stedi's pay-as-you-go rates (re-runs on the same morning don't check again). Open Dental API $15 per location. No texts. About $113 a month plus your n8n server, against roughly 80 staff hours if each check took 12 minutes by hand.

Setup

  1. Read the 'HIPAA, TCPA and BAAs' section on the dental hub first. Eligibility responses are PHI: use a Stedi HIPAA account (Stedi's BAA covers HIPAA accounts only) and a BAA-covered n8n server.
  2. In Stedi, create a production API key and put it in an n8n credential (STEDI_API_KEY in our mock setup). Map Open Dental carriers to Stedi payer IDs in STEDI_PAYER_MAP (JSON: carrier name or Open Dental ElectID to Stedi payer ID); carriers without a mapping use their ElectID.
  3. Set PRACTICE_NAME and PRACTICE_NPI (the billing provider sent to the payer), PRACTICE_TZ, VERIFY_DAYS_AHEAD (default 3) and LOW_MAX_REMAINING (default $300).
  4. Give the workflow an Open Dental API key with the $15 tier (InsuranceSimple for insverifies, Comm for commlogs).
  5. Create the checklist Sheet in BAA-covered Google Workspace and swap the mock sheet URL for n8n's Google Sheets node. Run a week with the Open Dental write disabled and compare the checklist with your own calls. The workflow also has a manual 'run now' webhook used by our tests. Delete it, or put it behind n8n's header authentication, before you go live.

Download

Download dental-insurance-verification-prep.zip

Contains the n8n workflow, the mock data, the test runner with its last results, and the mock server we tested against. All keys in .env.example are fake.

Test result

Every workflow was imported into n8n 2.45.4 and run end to end on our test machine. Open Dental's API, Twilio, Stedi, Google Sheets and the email sender (Resend) were replaced by a local mock server built from their official API docs: it checks the same request shapes and required fields, enforces Open Dental's request throttle (HTTP 429) and records every call. We also compared the mock's field names with Open Dental's public test database (read-only GET requests with the test key Open Dental publishes, no writes). These recipes use no AI model; every decision is a written rule. Patients, plans and phone numbers are invented. Verified with mocks; no real patient data used, and nothing was sent to a real phone, patient, payer or account.

4/4 scenarios passed (run 2026-10-28)

PASSI1Monday 7 am, visits on Thursday: active, low remaining maximum, inactive, payer error (bad member ID), no insurance
PASSI2Re-run the same morning: no second (billable) eligibility call
PASSI3Child on a parent's plan: request carries the subscriber's member ID + a dependent block
PASSI4Missing birth date -> Stedi 400 shown as 'check failed' (no crash, nothing marked verified); Open Dental 429 retried

Actual output from the test run

Checklist rows written in scenario I1 (invented patients and plans; member IDs show the last 4 digits only). Names and numbers are invented.

[
 {
  "appointment": "2026-11-05 09:00",
  "patient": "Ana Lopez",
  "PatNum": 101,
  "plan": "Primary: Delta Dental (fictional plan) / Lakeside Schools",
  "member": "...0001",
  "subscriber": "self",
  "status": "Active",
  "deductible_left": "$50",
  "annual_max_left": "$1,210",
  "coinsurance": "preventive 0%, basic 20%, endo 20%, crowns 50%",
  "flags": "",
  "next_step": "None - verified"
 },
 {
  "appointment": "2026-11-05 10:00",
  "patient": "Carla Nguyen",
  "PatNum": 103,
  "plan": "Primary: MetLife Dental (fictional plan) / Rivet Manufacturing",
  "member": "...2033",
  "subscriber": "self",
  "status": "Active",
  "deductible_left": "$0",
  "annual_max_left": "$120",
  "coinsurance": "preventive 0%, basic 20%, endo 50%, crowns 50%",
  "flags": "Only $120 of the annual maximum left",
  "next_step": "Review the estimate with the patient at check-in"
 },
 {
  "appointment": "2026-11-05 11:00",
  "patient": "Erin Walsh",
  "PatNum": 105,
  "plan": "Primary: Cigna Dental (fictional plan) / County Employees",
  "member": "...1005",
  "subscriber": "self",
  "status": "Inactive",
  "deductible_left": "",
  "annual_max_left": "",
  "coinsurance": "",
  "flags": "Coverage not active",
  "next_step": "Call the patient before the visit (new plan? self-pay?)"
 },
 {
  "appointment": "2026-11-05 13:00",
  "patient": "Grace Kim",
  "PatNum": 107,
  "plan": "Primary: MetLife Dental (fictional plan) / Rivet Manufacturing",
  "member": "...510X",
  "subscriber": "self",
  "status": "Payer error 72",
  "deductible_left": "",
  "annual_max_left": "",
  "coinsurance": "",
  "flags": "Invalid/Missing Subscriber/Insured ID - Check the member ID on the insurance card.",
  "next_step": "Ask the patient for the card / call (800)555-0102"
 },
 {
  "appointment": "2026-11-05 14:00",
  "patient": "Iris Chen",
  "PatNum": 109,
  "plan": "none on file",
  "status": "No insurance on file",
  "flags": "Confirm self-pay or ask for a card at check-in",
  "next_step": "Front desk"
 }
]

Verified with mocks

  • Monday 7 am run for Thursday's visits: active coverage (marked verified in Open Dental with a commlog), low remaining annual maximum (flagged), inactive coverage, a payer error for a bad member ID and a patient with no insurance (all flagged for a person).
  • Same-morning re-run makes no second billable eligibility call.
  • Child on a parent's plan: the request carries the subscriber's member ID and a dependent block.
  • A missing birth date returns a Stedi 400 that shows as 'check failed' (no crash, nothing marked verified); Open Dental 429 replies are retried.
  • The checklist shows only the last 4 digits of member IDs.
  • Verified with mocks; no real patient data used.

Documented from official API docs only

Built to the vendor's documentation but not run against a live account. Check these on yours.

  • Stedi real-time eligibility, legacy JSON endpoint (POST /2024-04-01/change/medicalnetwork/eligibility/v3, tradingPartnerServiceId). Stedi released a redesigned endpoint (2026-06-01/eligibility-check) on 25 Sep 2026 and says the legacy one will not be deprecated and can stay in production. Moving to the new one changes the request shape; Stedi has a migration guide.
  • Open Dental API v1 patplans, inssubs, insplans, carriers, insverifies PUT and commlogs (from Open Dental's API docs; read paths compared with the public test database).
  • Dental benefit details (frequencies, waiting periods, downgrades) vary by payer and often aren't in the 271 response. Not parsed.

Limitations

  • No patient cost text. A cost estimate is billing information (PHI) and isn't one of the TCPA's exempted healthcare messages, so estimates stay a staff conversation. (Our keyword plan described this as a no-payer-call checklist; we chose a real eligibility check through Stedi instead, mocked in our tests.)
  • An eligibility response is not a guarantee of payment. Payers can still deny a claim.
  • Built for Open Dental only. Dentrix, Eaglesoft, Curve and NexHealth versions are not built, and we make no claim that this works with them.
  • Business associate agreements are your job: self-host n8n on infrastructure covered by a BAA (n8n does not offer one for n8n Cloud), accept Google's Workspace BAA before any patient name goes into a Sheet, and ask your compliance officer whether your texting setup needs Twilio's HIPAA offering (Security or Enterprise Edition plus a BAA, sales-quoted). Patient replies can contain health details even when our texts do not.
  • Not a compliance guarantee. We designed around HIPAA and the TCPA (generic texts with no treatment, fee or name; consent from Open Dental; staff lists kept in BAA-covered Google Workspace), but whether your setup is compliant depends on your contracts, your server and how your staff use it. Check with your compliance officer or counsel and your state dental board before you turn it on.