52 min readDeterministic automation

Implementation Guide: Auto-submit superbills to insurers on session completion

Step-by-step implementation guide for deploying AI to auto-submit superbills to insurers on session completion for Allied & Mental Health clients.

Hardware Procurement

Next-Generation Firewall / UTM Appliance

FortinetFortiGate 40F (FG-40F)Qty: 1

$250–$400 MSP distributor cost (Ingram Micro, D&H) / $500–$700 suggested resale including initial configuration

Provides NGFW with IDS/IPS, content filtering, SSL inspection, and site-to-site VPN for the practice network. Required for HIPAA Security Rule compliance — protects ePHI in transit between practice workstations and cloud-hosted EHR, clearinghouse, and payer portals. FortiGuard Security Services subscription adds threat intelligence, web filtering, and antivirus.

Next-Generation Firewall / UTM Appliance (Alternative)

Cisco MerakiMX67 (MX67-HW)Qty: 1

$595–$800 MSP distributor cost / $900–$1,200 suggested resale with first-year Enterprise license

Cloud-managed alternative to FortiGate. Recommended if the practice already uses Meraki networking or if the MSP's RMM stack is Meraki-centric. Includes SD-WAN, content filtering, and Auto VPN. Annual license renewal provides ongoing MSP recurring revenue.

Wireless Access Point

FortinetFortiAP 231G (FAP-231G)Qty: 1

$200–$350 MSP cost / $400–$550 suggested resale

VLAN-capable wireless access point for practice. Enables network segmentation: clinical devices on a secured VLAN, guest/patient Wi-Fi on an isolated VLAN. Managed centrally through the FortiGate.

Uninterruptible Power Supply

APC by Schneider ElectricBack-UPS Pro 1500VA (BR1500MS2)Qty: 1

$180–$220 MSP cost / $280–$350 suggested resale with installation

Battery backup for firewall, network switch, and primary workstation. Ensures claim submissions in progress are not interrupted by power fluctuations. Provides 10–15 minutes of runtime for graceful shutdown or ride-through of brief outages.

Business Workstation

Dell TechnologiesOptiPlex 3000 Micro (or Lenovo ThinkCentre M70q Gen 4)Qty: 1

$500–$800 MSP cost per unit / $700–$1,100 suggested resale per unit

Modern business-class endpoint for front-desk billing staff. Must support TPM 2.0 for BitLocker full-disk encryption (HIPAA requirement). Only required if existing workstations are aging or lack TPM 2.0.

Managed Network Switch

FortinetFortiSwitch 108F (FS-108F)Qty: 1

$150–$250 MSP cost / $300–$400 suggested resale

8-port managed PoE switch for VLAN segmentation between clinical and administrative networks. Powers the FortiAP wirelessly. Managed through the FortiGate for unified security policy enforcement.

Software Procurement

SimplePractice Plus Plan (Primary EHR — Mental Health)

SimplePracticeSaaS per-provider monthlyQty: Per provider

$99/month per provider. Additional providers at $59/month each. Claims at $0.25/claim. Client pays direct to SimplePractice.

All-in-one EHR, scheduling, telehealth, documentation, and insurance billing platform for mental health practices. The Plus plan includes electronic claim submission, ERA auto-posting, and superbill auto-generation. The built-in claims workflow handles the entire superbill→837P→clearinghouse→payer chain without external middleware.

TherapyNotes (Alternative EHR — Mental Health)

TherapyNotesSaaS per-provider monthlyQty: Per provider

Solo: $69/month; Group: $79/month + $50/month per additional clinician. Claims at $0.14/claim, ERA at $0.14/claim. Client pays direct.

Strong alternative to SimplePractice with lower per-claim costs. After note completion, the To-Do list automatically creates a reminder to submit the electronic claim. Tighter integration of clinical notes with claim generation. Recommended when per-claim cost is a concern (high-volume practices).

$129.99/month per provider (Plus plan). Group plans available at volume discounts. Client pays direct.

The only therapy/allied-health EHR with a full GraphQL API and webhook support. Required for Path B (custom integration) implementations. Webhooks fire on appointment status change, note signing, and other events — enabling automated claim submission through external middleware to any clearinghouse. Best choice for multi-disciplinary practices (dietitians, OTs, SLPs combined with therapists).

$30–$150/month depending on plan. Built-in clearinghouse at $15/month for unlimited claims. Client pays direct.

Purpose-built EHR for allied health professionals (registered dietitians, occupational therapists, speech-language pathologists). Includes built-in clearinghouse via Availity with unlimited claims and unlimited insurers. Simplest all-in-one option for non-mental-health allied practices.

$0/month. Free for all providers. Widest payer connectivity (1M+ providers, $2.4T in claims/year).

Primary clearinghouse for claims submission and eligibility verification. Handles ANSI X12 837P formatting and routing to payers. Provides ERA (Electronic Remittance Advice) for payment reconciliation. Free tier is sufficient for most SMB practices. Used when the EHR's built-in clearinghouse does not support a required payer.

Free Basic plan with 100 free eligibility checks and claims/month. Paid plans from $2,000/month for higher volume. Client pays direct.

JSON-native, developer-friendly clearinghouse API. Required only for Path B custom integrations where the MSP is building direct API-to-clearinghouse pipelines. Provides real-time eligibility checks, claims submission, ERA retrieval, and payer enrollment via RESTful APIs. Not needed for Path A (native EHR auto-claim) implementations.

Keragon (HIPAA-Compliant Workflow Automation)

KeragonStarter Plan (SaaS Monthly)Qty: 1 subscription

$99/month (billed annually). Higher tiers for more workflows. MSP can resell or client pays direct.

No-code healthcare automation middleware. Connects EHR webhooks (e.g., Healthie) to clearinghouse APIs (e.g., Stedi/Availity). Includes signed BAA, SOC 2 Type II compliance, encrypted data handling, and audit logging. Required only for Path B custom integrations. Not needed if using EHR's built-in auto-claim.

Microsoft 365 Business Premium

MicrosoftSaaS per-user monthlyQty: per user/month

$22/user/month. MSP resells through CSP program at ~$18–$20/user cost. Suggested resale at $25–$30/user/month (bundled with management).

Provides Exchange Online email (HIPAA-compliant with BAA), Microsoft Intune MDM for endpoint management, Azure AD MFA, Microsoft Defender for Business endpoint protection, and Power Automate for workflow automation. Single platform covers email, device management, identity, and automation needs for the practice.

Huntress Managed EDR

HuntressPer-endpoint monthly

$3–$5/endpoint/month MSP cost / $8–$12/endpoint suggested resale

Managed endpoint detection and response layered on top of Microsoft Defender. Provides 24/7 SOC monitoring, threat hunting, and incident response. Critical for HIPAA compliance — demonstrates active threat monitoring for endpoints handling ePHI.

Datto BCDR (or Axcient x360Recover)

Datto (Kaseya) / AxcientDatto BCDR / Axcient x360RecoverQty: Per-device monthly

$20–$50/device/month MSP cost / $40–$80/device suggested resale depending on retention and RTO requirements

HIPAA-compliant cloud backup with BAA. Backs up workstation configurations and any local data. AES-256 encryption at rest and in transit. Required for HIPAA Security Rule contingency planning requirements.

Cisco Umbrella (or DNSFilter)

Cisco / DNSFilterQty: Per-user monthly

$2–$4/user/month MSP cost / $5–$8/user suggested resale

DNS-layer security filtering. Blocks malicious domains, phishing sites, and command-and-control callbacks at the network layer. Additional security layer for endpoints accessing EHR and clearinghouse portals.

Prerequisites

  • Business-class internet connection with minimum 25 Mbps down / 10 Mbps up and provider SLA (no residential connections)
  • Practice must have a valid NPI (Type 1 for individual providers, Type 2 for group practice) registered with NPPES
  • Each rendering provider must be credentialed and enrolled with target insurance payers (Aetna, BCBS, Cigna, UHC, etc.) — this is a clinical/administrative prerequisite, not an IT task
  • Practice must have completed or be willing to complete payer ERA (Electronic Remittance Advice) and EFT (Electronic Funds Transfer) enrollment for each insurer — allow 2–6 weeks per payer
  • Practice tax ID (EIN) and taxonomy codes must be current and match NPI registry entries exactly
  • Signed Business Associate Agreements (BAAs) must be in place between the practice and: the MSP, the EHR vendor, the clearinghouse, and any middleware/automation platform
  • HIPAA Security Risk Assessment must be completed (or scheduled) — this is a federal requirement and an MSP service opportunity
  • All clinical staff must have individual EHR login credentials (no shared accounts) — HIPAA audit trail requirement
  • Practice must have standardized clinical note templates that include required billing fields: session start/stop times, CPT code selection, ICD-10 diagnosis codes to highest specificity, and place of service
  • Windows 10/11 Pro (for BitLocker) or macOS (for FileVault) on all endpoints — Windows Home editions cannot be used due to lack of BitLocker/GPO support
  • TPM 2.0 chip required on all Windows workstations for BitLocker full-disk encryption
  • Practice must designate a HIPAA Privacy Officer and Security Officer (can be the same person in small practices)
  • If the practice treats substance use disorder (SUD) patients, a signed single TPO consent form compliant with 42 CFR Part 2 (updated February 2024) must be on file for each SUD patient before automated claim submission

Installation Steps

Step 1: Site Assessment and Security Baseline Audit

Perform an on-site or remote assessment of the practice's current IT environment. Document all existing hardware (workstations, network equipment, printers), software (current EHR, billing software, Office suite), internet connectivity, and security posture. Identify gaps against HIPAA Security Rule requirements. Catalog all insurance payers the practice bills to and verify ERA/EFT enrollment status for each. Review the last 90 days of claim submissions to identify rejection patterns and common errors.

Network inventory scan and BitLocker encryption status check
bash
# Run network scan to inventory all connected devices
nmap -sP 192.168.1.0/24 > practice_network_inventory.txt

# Check Windows endpoint encryption status (run on each workstation)
manage-bde -status C:
  • Navigate to https://www.ssllabs.com/ssltest/ from each workstation to verify TLS 1.2+ support
Note

This step typically takes 2–4 hours on-site for a small practice. Use your MSP's standard HIPAA risk assessment template. Document findings in a formal report — this becomes the basis for the HIPAA Security Risk Assessment deliverable (billable at $1,500–$5,000). Pay special attention to any workstations running Windows Home editions or lacking TPM 2.0 — these must be upgraded or replaced.

Step 2: Network Infrastructure Deployment

Install and configure the HIPAA-compliant network infrastructure. Deploy the FortiGate 40F firewall (or Meraki MX67), configure VLANs for clinical vs. guest traffic, set up the FortiSwitch for wired connections, deploy the FortiAP for wireless, and connect the UPS. Configure firewall security policies: IDS/IPS enabled, SSL inspection for outbound traffic (with exceptions for EHR domains if certificate pinning is used), content filtering to block non-business categories, and DNS filtering.

FortiGate 40F Initial Configuration via CLI (connect via console cable)
fortios
config system interface
  edit port1
    set alias 'WAN'
    set mode dhcp
    set allowaccess ping https ssh
  next
  edit port2
    set alias 'LAN-Clinical'
    set ip 10.10.10.1 255.255.255.0
    set allowaccess ping https ssh
    set role lan
  next
  edit port3
    set alias 'LAN-Guest'
    set ip 10.10.20.1 255.255.255.0
    set allowaccess ping
    set role lan
  next
end

# Enable IPS
config ips sensor
  edit 'clinical-ips'
    config entries
      edit 1
        set location server client
        set severity high critical
        set status enable
        set action block
      next
    end
  next
end

# Create VLAN for clinical network
config system dhcp server
  edit 1
    set interface port2
    config ip-range
      edit 1
        set start-ip 10.10.10.100
        set end-ip 10.10.10.200
      next
    end
    set netmask 255.255.255.0
    set gateway 10.10.10.1
    set dns-server1 208.67.222.222
    set dns-server2 208.67.220.220
  next
end

# Configure firewall policy: Clinical LAN to Internet (HTTPS only to EHR/clearinghouse)
config firewall policy
  edit 1
    set name 'Clinical-to-Internet'
    set srcintf port2
    set dstintf port1
    set srcaddr 'all'
    set dstaddr 'all'
    set action accept
    set schedule 'always'
    set service 'HTTPS' 'DNS'
    set utm-status enable
    set ips-sensor 'clinical-ips'
    set ssl-ssh-profile 'certificate-inspection'
    set av-profile 'default'
    set webfilter-profile 'default'
    set logtraffic all
    set nat enable
  next
end
Note

If using Cisco Meraki MX67 instead, all configuration is done through the Meraki Dashboard (dashboard.meraki.com) — no CLI needed. Ensure the FortiGate firmware is updated to the latest stable release before configuring. Register the firewall with FortiCloud for remote management. Save the admin credentials in the MSP's password vault (e.g., IT Glue, Hudu). Configure SNMP or FortiCloud monitoring alerts to your RMM.

Step 3: Endpoint Hardening and MDM Enrollment

Secure all practice workstations per HIPAA requirements. Enable full-disk encryption (BitLocker on Windows, FileVault on macOS), enroll devices in Microsoft Intune MDM, enforce MFA on all user accounts, deploy Huntress EDR agent, and configure automatic OS and browser updates.

Enable BitLocker, install Huntress EDR agent, configure Windows Update, and enroll device in Intune — run as Administrator on each Windows workstation
powershell
# Enable BitLocker via PowerShell (run as Administrator on each Windows workstation)
Enable-BitLocker -MountPoint 'C:' -EncryptionMethod XtsAes256 -UsedSpaceOnly -RecoveryPasswordProtector

# Get BitLocker recovery key and save to Azure AD
BackupToAAD-BitLockerKeyProtector -MountPoint 'C:' -KeyProtectorId ((Get-BitLockerVolume -MountPoint 'C:').KeyProtector | Where-Object {$_.KeyProtectorType -eq 'RecoveryPassword'}).KeyProtectorId

# Install Huntress agent (replace with your MSP's Huntress org key and account key)
# Download installer from Huntress dashboard
msiexec /i HuntressInstaller.msi ACCT_KEY="your_account_key" ORG_KEY="practice_name" /quiet

# Verify Huntress agent is running
Get-Service HuntressAgent | Select-Object Name, Status

# Configure Windows Update via PowerShell
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -Name 'NoAutoUpdate' -Value 0
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -Name 'AUOptions' -Value 4

# Enroll device in Intune (user will be prompted during next sign-in)
# Or push enrollment via Intune Autopilot if devices are new
Start-Process 'ms-device-enrollment:?mode=mdm'
Note

BitLocker recovery keys MUST be stored in Azure AD and in your MSP's documentation platform (IT Glue/Hudu). Never store recovery keys only on the local device. For macOS endpoints, enable FileVault via MDM profile in Intune. Configure Intune compliance policies to require encryption, require screen lock after 5 minutes, and block devices that fail compliance checks from accessing M365/EHR.

Step 4: Microsoft 365 Business Premium Configuration

Configure the practice's Microsoft 365 tenant for HIPAA compliance. Execute the Microsoft BAA (available in the M365 admin center under Settings > Org settings > Security & privacy), enable MFA for all users via Conditional Access, configure email encryption with Microsoft Purview, and set up Power Automate licenses for workflow automation if needed for Path B implementations.

Connect to Microsoft 365 and create Conditional Access policy requiring MFA for all users
powershell
# Connect to Microsoft 365 via PowerShell
Install-Module -Name Microsoft.Graph -Force
Connect-MgGraph -Scopes 'Policy.ReadWrite.ConditionalAccess','User.ReadWrite.All'

# Create Conditional Access policy requiring MFA for all users
$params = @{
  displayName = 'Require MFA for all users'
  state = 'enabled'
  conditions = @{
    users = @{ includeUsers = @('All') }
    applications = @{ includeApplications = @('All') }
  }
  grantControls = @{
    builtInControls = @('mfa')
    operator = 'OR'
  }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $params
  • Navigate to: admin.microsoft.com > Settings > Org settings > Security & privacy > HIPAA BAA
  • Accept the BAA and download a copy for records
Note

The Microsoft HIPAA BAA is self-service in the M365 Admin Center — no sales call needed. Download a PDF copy and store it in the practice's compliance documentation. Ensure at least one break-glass admin account exists with MFA via hardware key (not SMS) in case of Authenticator app failures. Configure mail flow rules to auto-encrypt outbound emails containing PHI keywords.

Step 5: EHR Platform Selection and Account Setup

Based on the site assessment findings, select and set up the appropriate EHR platform. For most mental health solo/small group practices, SimplePractice Plus or TherapyNotes is recommended. For allied health (dietitians, OTs, SLPs), Kalix is recommended. For practices needing API customization, Healthie is the only viable option. Create the practice account, add all provider profiles with NPI, taxonomy codes, and credentials.

  • SimplePractice signup: https://www.simplepractice.com/signup
  • TherapyNotes signup: https://www.therapynotes.com/signup
  • Healthie signup: https://www.gethealthie.com/signup
  • Kalix signup: https://www.kalix.health/signup
Note

If the practice already has an EHR, skip this step and proceed to Step 6 (configuring auto-claim within the existing platform). DO NOT migrate EHRs solely for this project unless the current platform cannot support electronic claims at all. EHR migration is a separate, larger project. For SimplePractice, the Plus plan ($99/mo) is required for insurance billing — Starter and Essential plans have limited claim functionality.

Step 6: Clearinghouse Enrollment and Payer Registration

Enroll the practice with the clearinghouse (built into most EHRs, or Availity/Office Ally for standalone). Then enroll each insurance payer for electronic claims submission (837P) and Electronic Remittance Advice (ERA/835). This is the longest step — individual payer enrollments can take 2–6 weeks. Start this step immediately and run it in parallel with other configuration work.

  • For SimplePractice built-in clearinghouse: Navigate to: Settings > Insurance > Payer Setup
  • Click 'Add Payer' and search by payer name or Payer ID
  • Complete enrollment form for each payer (requires practice NPI, Tax ID, provider credentials)
  • Submit ERA enrollment separately for each payer
  • For Availity (standalone clearinghouse): Register at https://www.availity.com/Availity-Essentials-Registration
  • Complete organization registration with NPI and Tax ID
  • Add users and assign roles
  • Submit payer-specific enrollment requests through Availity portal

Common Payer IDs for clearinghouse enrollment

1
Common Payer IDs to enroll: Aetna: 60054 Anthem BCBS: 00230 (varies by state) Cigna: 62308 UnitedHealthcare: 87726 Humana: 61101 Magellan: 77027 Optum/UBH: 87726 Tricare: 99726 Medicare: varies by MAC region
Critical

ERA/EFT enrollment is separate from claims submission enrollment. The practice can submit claims electronically before ERA enrollment is complete, but will receive paper EOBs until ERA is active. Track each payer enrollment status in a spreadsheet — payers approve at different rates. Some payers (e.g., certain Medicaid MCOs) require additional credentialing steps. Confirm that the practice's NPI, Tax ID, and provider names match EXACTLY between the NPI registry (NPPES), the EHR, and the clearinghouse — mismatches are the #1 cause of enrollment rejections.

Step 7: Configure Superbill Templates and CPT/ICD-10 Code Libraries

Build standardized superbill templates in the EHR that auto-populate with the correct billing codes based on session type. Create CPT code favorites for the practice's most common services and ICD-10 diagnosis code favorites. Configure session-type-to-CPT-code mappings so clinicians only need to select the session type and the correct billing code is pre-populated.

Standard Mental Health CPT Codes
text
90791 - Psychiatric Diagnostic Evaluation (intake, no medical services)
90792 - Psychiatric Diagnostic Evaluation WITH Medical Services (psychiatrists only)
90832 - Psychotherapy, 16-37 minutes
90834 - Psychotherapy, 38-52 minutes (most common)
90837 - Psychotherapy, 53+ minutes
90846 - Family psychotherapy without patient present
90847 - Family psychotherapy with patient present
90853 - Group psychotherapy
90785 - Interactive complexity add-on (use with 90832/90834/90837)
90839 - Psychotherapy for crisis, first 60 minutes
90840 - Psychotherapy for crisis, each additional 30 minutes
Telehealth Modifiers and Place of Service Codes
text
Modifier 95    - Synchronous audio-video telehealth
Modifier 93 or FQ - Audio-only telehealth
Place of Service 10 - Telehealth (patient at home)
Place of Service 02 - Telehealth (patient at qualifying facility)
Place of Service 11 - Office (in-person)
Allied Health CPT Codes (if applicable)
text
97530 - Therapeutic activities (OT)
97110 - Therapeutic exercises (PT/OT)
92507 - Speech therapy treatment
97802 - Medical nutrition therapy initial (RD)
97803 - Medical nutrition therapy reassessment (RD)
Common ICD-10 Diagnosis Codes (Mental Health)
text
F41.1  - Generalized anxiety disorder
F41.0  - Panic disorder
F32.0  - Major depressive disorder, single episode, mild
F32.1  - Major depressive disorder, single episode, moderate
F33.0  - Major depressive disorder, recurrent, mild
F33.1  - Major depressive disorder, recurrent, moderate
F43.10 - Post-traumatic stress disorder, unspecified
F43.23 - Adjustment disorder with mixed anxiety and depressed mood
F90.0  - ADHD, predominantly inattentive type
F90.1  - ADHD, predominantly hyperactive type
F90.2  - ADHD, combined type
Critical

ICD-10 codes must be coded to the highest level of specificity. F41 (anxiety disorder, unspecified) will be rejected by most payers — F41.1 (generalized anxiety disorder) is required. Work with the lead clinician to build the practice's top 20 diagnosis codes into favorites. For CPT 90834 vs 90837: the session must be documented with start and stop times. 90834 covers 38–52 minutes of psychotherapy; 90837 requires 53+ minutes. Incorrect time documentation is a top audit trigger. Configure the EHR note template to require start/stop time fields before the note can be signed.

Step 8: Enable Auto-Claim Generation on Note Signing

Configure the EHR to automatically generate and queue an insurance claim when a clinician signs/completes their session note. This is the core automation step. In most EHRs, this is a settings toggle or workflow rule — not custom code. The goal is: clinician signs note → system validates required fields → superbill auto-generates → claim auto-queues for submission → claim submits to clearinghouse within minutes or on the next batch cycle.

  • SimplePractice Configuration — Step 1: Navigate to Settings > Billing > Insurance
  • SimplePractice Configuration — Step 2: Under 'Claim Defaults', configure: Default Place of Service: 11 (Office) or 10 (Telehealth); Auto-generate claim when appointment is completed: ENABLE; Default billing provider for each clinician
  • SimplePractice Configuration — Step 3: Under each Appointment Type, map to the corresponding CPT code
  • SimplePractice Configuration — Step 4: Navigate to Settings > Billing > Clearinghouse
  • SimplePractice Configuration — Step 5: Verify clearinghouse enrollment status shows 'Active' for each payer
  • SimplePractice Configuration — Step 6: Under 'Claim Submission', set to 'Auto-submit' (if available) or 'Queue for batch'
  • TherapyNotes Configuration — Step 1: Navigate to Billing & Insurance settings
  • TherapyNotes Configuration — Step 2: Under 'Electronic Claims', enable electronic claim submission
  • TherapyNotes Configuration — Step 3: Configure To-Do list notifications: after note is signed, auto-generate claim task
  • TherapyNotes Configuration — Step 4: Set batch submission schedule (daily at 6 PM recommended)
  • TherapyNotes Configuration — Step 5: Configure ERA auto-posting: Settings > ERA/EFT > Enable auto-posting
  • Healthie Configuration (API Path) — Step 1: Navigate to Settings > Integrations > Webhooks
  • Healthie Configuration (API Path) — Step 2: Create webhook for event: 'appointment.completed'
  • Healthie Configuration (API Path) — Step 3: Set webhook URL to your Keragon workflow endpoint
  • Healthie Configuration (API Path) — Step 4: See Custom AI Components section for the webhook handler specification
  • TheraPlatform Configuration — Step 1: Navigate to Settings > Billing > Automation
  • TheraPlatform Configuration — Step 2: Enable 'Automatically generate claims after session notes are signed'
  • TheraPlatform Configuration — Step 3: Set submission method to 'Electronic'
  • TheraPlatform Configuration — Step 4: Configure batch schedule or real-time submission
Note

IMPORTANT: The auto-claim feature does NOT mean claims submit without any human review. Best practice is a 'generate and queue' model: claims are auto-generated when notes are signed, then a billing staff member reviews the queue once daily (5–10 minutes) before batch submission. This catches obvious errors (wrong CPT code, missing modifier) before they reach the payer. Configure the EHR to flag claims that fail validation rules (missing diagnosis, missing authorization number, etc.) and hold them for review rather than auto-submitting bad claims. SimplePractice and TheraPlatform support near-real-time auto-submission; TherapyNotes uses a batch model with manual review step.

Step 9: Configure ERA Auto-Posting and Denial Alerts

Set up Electronic Remittance Advice (ERA/835) auto-posting so that when insurance payments are received, they automatically post to the correct patient account in the EHR. Configure denial alerts so that rejected or denied claims trigger immediate notifications to billing staff for follow-up. This closes the revenue cycle loop.

SimplePractice ERA Configuration

1
Navigate to Settings > Billing > Insurance > ERA
2
For each enrolled payer, verify ERA status shows 'Active'
3
Enable auto-posting: ERAs will auto-match to claims and post payments
4
Configure denial notification: Settings > Notifications > enable 'Claim denied' alerts
5
Set up email notifications to billing@practice.com for all denials

TherapyNotes ERA Configuration

1
Navigate to Billing > ERA/EFT Settings
2
Verify ERA enrollment per payer
3
Enable auto-posting of ERA payments
4
Configure the Claim Status dashboard to show denied/rejected claims prominently

Daily Claims Monitoring Checklist

Note

Common ERA denial reason codes to train staff on: CO-4 (procedure code inconsistent with modifier), CO-16 (claim lacks information), CO-29 (timely filing limit exceeded), CO-197 (precertification/authorization required), PR-1 (deductible), PR-2 (coinsurance), PR-3 (co-payment). If auto-posting is not available in the EHR tier, ERA files can be downloaded from the clearinghouse portal and manually imported. Auto-posting saves the billing team 30–60 minutes per day in a 5-provider practice.

If the practice treats any substance use disorder (SUD) patients, configure a consent verification checkpoint in the claim submission workflow. Under the updated 42 CFR Part 2 (effective April 2024, compliance required by February 2026), a single TPO consent allows claims to flow through the normal pipeline. However, the system must verify that consent is on file before submitting claims containing SUD diagnosis codes.

  • In the EHR, create a custom field or tag for SUD patients with field name: 'Part 2 TPO Consent Status' and options: 'Consent on File' / 'Consent Pending' / 'No Consent'
  • SimplePractice: Use client tags or custom notes field
  • TherapyNotes: Use client alerts or custom demographic field
  • Healthie: Use custom form fields (accessible via API for automated checks)
  • Document the consent in the patient record with: date consent was signed, scope (Treatment, Payment, and Healthcare Operations / TPO), expiration date (if any), and patient signature confirmation
Billing workflow rule logic for 42 CFR Part 2 SUD consent check
plaintext
# Configure a billing workflow rule:
# IF diagnosis code starts with F10-F19 (substance-related disorders)
#   AND 'Part 2 TPO Consent Status' != 'Consent on File'
#   THEN hold claim for manual review (do not auto-submit)
Note

The 2024 update to 42 CFR Part 2 significantly simplified consent management. A single consent for all TPO now covers claims submission, and Part 2 records no longer need to be segregated. However, if a patient has NOT signed a TPO consent, submitting a claim with SUD diagnosis codes is a federal violation. The hold-for-review workflow is a critical safety net. Not all practices treat SUD — if the practice does not, this step can be abbreviated to awareness training only.

Step 11: End-to-End Testing with Live Test Claims

Submit test claims through the complete pipeline to verify the automation works correctly. Start with a single payer (the practice's most common insurer), submit 3–5 test claims, verify receipt at the clearinghouse, monitor for acceptance/rejection, and verify ERA posting when payments arrive. Then expand to all enrolled payers.

1
Test 1: Standard in-person psychotherapy claim — CPT: 90834 (45-min psychotherapy), ICD-10: F41.1 (Generalized anxiety disorder), POS: 11 (Office), Modifiers: None. Expected: Clean claim acceptance.
2
Test 2: Telehealth session claim — CPT: 90837 (53+ min psychotherapy), ICD-10: F32.1 (Major depressive disorder, single episode, moderate), POS: 10 (Telehealth - patient at home), Modifier: 95 (synchronous audio-video). Expected: Clean claim acceptance.
3
Test 3: Audio-only telehealth claim — CPT: 90834, ICD-10: F43.10 (PTSD), POS: 10, Modifier: FQ (audio-only). Expected: Verify payer accepts audio-only modifier.
4
Test 4: Initial evaluation claim — CPT: 90791 (Psychiatric diagnostic evaluation), ICD-10: Appropriate diagnosis, POS: 11. Expected: Clean claim acceptance.
5
Test 5: Intentional error claim (for validation testing) — CPT: 90834 with missing diagnosis code. Expected: EHR or clearinghouse should REJECT before reaching payer.
1
Confirm claim appears in EHR's 'Submitted Claims' queue
2
Check clearinghouse portal for claim receipt (within 1–4 hours)
3
Check clearinghouse for 277CA (claim acknowledgment) response
4
Monitor for 835 (ERA) response (typically 7–21 days)
5
Verify ERA auto-posts correctly to patient account
Note

Test claims are REAL claims submitted for REAL patient encounters — there is no 'sandbox' mode with most clearinghouses for production payer enrollment. Coordinate with the practice to identify 3–5 upcoming sessions to use as test cases. The intentional error test (Test 5) is critical: it verifies that the validation layer catches problems before they reach the payer. If bad claims pass through validation, tighten the EHR's required-fields configuration. Allow 2–3 weeks for the full test cycle (claim submission through ERA receipt).

Step 12: Staff Training and Workflow Documentation

Train all clinical and administrative staff on the new automated billing workflow. Clinicians must understand that signing their note now directly triggers a billing event — note quality and completeness are no longer just clinical concerns, they are revenue cycle events. Administrative/billing staff must understand the claim queue review process, denial management, and exception handling.

  • No CLI commands — this is a training delivery step

Training Agenda (90-minute session)

Module 1: For Clinicians (45 minutes)

  • How note signing triggers auto-claim generation
  • Required fields that MUST be completed before signing: Session start and stop times (mandatory for 90832/90834/90837), CPT code selection (correct session duration mapping), ICD-10 diagnosis code (to highest specificity), Place of service (office vs telehealth), Telehealth modifiers (95 for video, FQ for audio-only)
  • Common mistakes that cause claim rejections
  • Timeline: notes should be signed within 24 hours of session

Module 2: For Billing Staff (45 minutes)

  • Daily claim queue review process (10-minute morning routine)
  • How to spot and fix common claim errors before submission
  • Denial notification workflow and escalation
  • ERA auto-posting verification
  • Secondary claim submission process
  • Patient responsibility collection workflow
  • Monthly reconciliation report generation
Note

Create a laminated quick-reference card for each clinician's desk/workspace with: (1) CPT code cheat sheet with time requirements, (2) top 10 ICD-10 codes for the practice, (3) telehealth modifier guide, (4) place-of-service code reference. Leave this physical artifact with the practice. Also create a 1-page 'What to do when a claim is denied' flowchart for billing staff. Schedule a 30-minute follow-up training session 2 weeks after go-live to address questions that arise from real-world use.

Custom AI Components

Healthie Webhook Claim Submission Workflow

Type: workflow

A deterministic automation workflow that triggers when a session note is signed in Healthie (via webhook), extracts the billing-relevant data via the Healthie GraphQL API, validates all required fields, formats the data for clearinghouse submission, and posts the claim to Stedi's Claims API (or routes to Availity). This component is ONLY needed for Path B implementations using Healthie as the EHR. Practices using SimplePractice, TherapyNotes, TheraPlatform, or Kalix should use the built-in auto-claim features instead.

Implementation:

Healthie Webhook Claim Submission Workflow — platform and trigger configuration
plaintext
# Healthie Webhook → Claim Submission Workflow
# Platform: Keragon (recommended) or Microsoft Power Automate
# Trigger: Healthie webhook event 'appointment.completed'

Architecture

  • Healthie Webhook (appointment.completed)
  • → Keragon Workflow Engine
  • → Step 1: Parse webhook payload, extract appointment_id
  • → Step 2: Query Healthie GraphQL API for full appointment + patient + provider data
  • → Step 3: Validate all required claim fields (fail-fast with alert if incomplete)
  • → Step 4: Check 42 CFR Part 2 consent status (if SUD diagnosis)
  • → Step 5: Map data to X12 837P field structure
  • → Step 6: POST to Stedi Claims API (or format EDI for Availity)
  • → Step 7: Log submission result and notify billing staff

Step 1: Healthie Webhook Configuration

1
In Healthie Admin: Settings > Integrations > Webhooks
2
Create new webhook:
3
Event: appointment.completed
4
URL: https://app.keragon.com/webhooks/your-workflow-id
5
Method: POST
6
Headers: { "Authorization": "Bearer <keragon_webhook_secret>" }

Step 2: Healthie GraphQL Query (executed by Keragon HTTP node)

Healthie GraphQL query to retrieve full appointment billing data
graphql
query GetAppointmentBillingData($appointmentId: ID!) {
  appointment(id: $appointmentId) {
    id
    date
    actual_duration
    appointment_type {
      name
      cpt_code {
        code
        description
      }
    }
    provider {
      id
      full_name
      npi_number
      tax_id
      taxonomy_code
      qualification
    }
    client {
      id
      first_name
      last_name
      date_of_birth
      gender
      address {
        line1
        line2
        city
        state
        zip
      }
      insurance_authorizations {
        id
        authorization_number
        insurance_plan {
          payer_name
          payer_id
          member_id
          group_number
          plan_name
          subscriber {
            first_name
            last_name
            date_of_birth
            relationship_to_patient
          }
        }
      }
    }
    form_answer_groups {
      form_answers {
        label
        answer
      }
    }
    cpt_code {
      code
      description
    }
    icd_codes {
      code
      description
    }
    place_of_service {
      code
      name
    }
    modifiers
  }
}

Step 3: Validation Rules (Keragon conditional logic or custom function)

Validation function
javascript
// all fields must pass before claim submission

// Validation function — all fields must pass before claim submission
function validateClaimData(appointment) {
  const errors = [];
  
  // Provider validation
  if (!appointment.provider.npi_number) errors.push('Missing provider NPI');
  if (!appointment.provider.tax_id) errors.push('Missing provider Tax ID');
  
  // Patient validation
  if (!appointment.client.first_name) errors.push('Missing patient first name');
  if (!appointment.client.last_name) errors.push('Missing patient last name');
  if (!appointment.client.date_of_birth) errors.push('Missing patient DOB');
  
  // Insurance validation
  const insurance = appointment.client.insurance_authorizations?.[0]?.insurance_plan;
  if (!insurance) errors.push('No insurance plan on file');
  if (!insurance?.member_id) errors.push('Missing insurance member ID');
  if (!insurance?.payer_id) errors.push('Missing payer ID');
  
  // Clinical validation
  if (!appointment.cpt_code?.code) errors.push('Missing CPT code');
  if (!appointment.icd_codes?.length) errors.push('Missing ICD-10 diagnosis code');
  if (!appointment.place_of_service?.code) errors.push('Missing place of service');
  
  // ICD-10 specificity check
  appointment.icd_codes?.forEach(icd => {
    if (icd.code.length < 4) {
      errors.push(`ICD-10 code ${icd.code} lacks specificity — must be 4+ characters`);
    }
  });
  
  // Time validation for time-based CPT codes
  const timeBasedCodes = ['90832', '90834', '90837'];
  if (timeBasedCodes.includes(appointment.cpt_code?.code)) {
    if (!appointment.actual_duration) {
      errors.push('Session duration required for time-based CPT code');
    }
    // Validate duration matches CPT code
    const duration = appointment.actual_duration;
    const cpt = appointment.cpt_code.code;
    if (cpt === '90832' && (duration < 16 || duration > 37)) {
      errors.push(`Duration ${duration}min does not match CPT 90832 (16-37 min)`);
    }
    if (cpt === '90834' && (duration < 38 || duration > 52)) {
      errors.push(`Duration ${duration}min does not match CPT 90834 (38-52 min)`);
    }
    if (cpt === '90837' && duration < 53) {
      errors.push(`Duration ${duration}min does not match CPT 90837 (53+ min)`);
    }
  }
  
  return { isValid: errors.length === 0, errors };
}
42 CFR Part 2 consent check
javascript
// evaluates SUD diagnosis codes and validates consent status before claim
// submission

// Check if any diagnosis codes are SUD-related (F10-F19)
function requiresPart2Consent(icdCodes) {
  const sudRange = /^F1[0-9]/;
  return icdCodes.some(icd => sudRange.test(icd.code));
}

// If SUD diagnosis, check consent status via Healthie custom field
// If consent not on file, HOLD claim and alert billing staff
if (requiresPart2Consent(appointment.icd_codes)) {
  const consentStatus = appointment.client.form_answer_groups
    ?.find(g => g.form_answers.some(a => a.label === 'Part 2 TPO Consent Status'))
    ?.form_answers.find(a => a.label === 'Part 2 TPO Consent Status')?.answer;
  
  if (consentStatus !== 'Consent on File') {
    // HALT workflow — send alert, do not submit claim
    return { action: 'HOLD', reason: '42 CFR Part 2 consent not confirmed for SUD patient' };
  }
}

Step 5 & 6: Submit to Stedi Claims API

Map appointment data to Stedi 837P claim payload and POST to Stedi Claims API
javascript
// Map to Stedi Professional Claims (837P) format
const claimPayload = {
  "tradingPartnerServiceId": insurance.payer_id,
  "submitter": {
    "organizationName": "Practice Name",
    "contactInformation": {
      "name": "Billing Department",
      "phoneNumber": "5551234567"
    }
  },
  "receiver": {
    "organizationName": insurance.payer_name
  },
  "billing": {
    "providerTaxId": appointment.provider.tax_id,
    "npi": appointment.provider.npi_number,
    "taxonomyCode": appointment.provider.taxonomy_code,
    "providerName": {
      "lastName": appointment.provider.full_name.split(' ').pop(),
      "firstName": appointment.provider.full_name.split(' ')[0]
    },
    "address": {
      "address1": "123 Practice St",
      "city": "Anytown",
      "state": "CA",
      "zip": "90210"
    }
  },
  "subscriber": {
    "memberId": insurance.member_id,
    "groupNumber": insurance.group_number,
    "firstName": insurance.subscriber.first_name,
    "lastName": insurance.subscriber.last_name,
    "dateOfBirth": insurance.subscriber.date_of_birth,
    "payerResponsibility": "primary"
  },
  "patient": {
    "firstName": appointment.client.first_name,
    "lastName": appointment.client.last_name,
    "dateOfBirth": appointment.client.date_of_birth,
    "gender": appointment.client.gender,
    "address": {
      "address1": appointment.client.address.line1,
      "city": appointment.client.address.city,
      "state": appointment.client.address.state,
      "zip": appointment.client.address.zip
    },
    "relationshipToSubscriber": insurance.subscriber.relationship_to_patient
  },
  "claimInformation": {
    "placeOfService": appointment.place_of_service.code,
    "diagnosisCodes": appointment.icd_codes.map((icd, i) => ({
      "code": icd.code,
      "type": i === 0 ? "principal" : "other"
    })),
    "serviceLines": [{
      "procedureCode": appointment.cpt_code.code,
      "modifiers": appointment.modifiers || [],
      "chargeAmount": getChargeAmount(appointment.cpt_code.code),
      "units": 1,
      "serviceDate": appointment.date,
      "diagnosisPointers": ["1"]
    }]
  }
};

// POST to Stedi Claims API
const response = await fetch('https://healthcare.stedi.com/2024-04-01/claims', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${STEDI_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(claimPayload)
});

const result = await response.json();
console.log('Claim submission result:', result);

Step 7: Logging and Notification

Audit log entry construction and conditional email notification for claim submission results
javascript
// Log to audit trail (Keragon provides built-in audit logging)
const logEntry = {
  timestamp: new Date().toISOString(),
  appointmentId: appointment.id,
  patientId: appointment.client.id,
  providerId: appointment.provider.id,
  cptCode: appointment.cpt_code.code,
  payerId: insurance.payer_id,
  claimStatus: result.status,
  claimId: result.claimId,
  errors: result.errors || []
};

// Send notification based on result
if (result.status === 'accepted') {
  // Send success summary to billing staff (daily digest recommended)
  sendEmail({
    to: 'billing@practice.com',
    subject: `Claim Submitted: ${appointment.client.last_name} - ${appointment.date}`,
    body: `Claim ${result.claimId} submitted successfully for ${appointment.cpt_code.code}.`
  });
} else {
  // Send immediate alert for failures
  sendEmail({
    to: 'billing@practice.com',
    subject: `CLAIM ERROR: ${appointment.client.last_name} - ${appointment.date}`,
    body: `Claim submission failed. Errors: ${result.errors.join(', ')}. Manual review required.`
  });
}

Keragon Workflow Configuration (No-Code Alternative)

If implementing in Keragon's no-code builder instead of custom code:

1
Trigger: Webhook Received (Healthie appointment.completed)
2
Action: HTTP Request → Healthie GraphQL API (query above)
3
Condition: Check all required fields present (built-in field validator)
4
Condition: Check SUD consent if F10-F19 diagnosis
5
Action: HTTP Request → Stedi Claims API (POST with mapped payload)
6
Condition: Branch on response status
7
Action (Success): Send email digest
8
Action (Failure): Send immediate alert email + log to error queue

Claims Rejection Monitor and Alert Dashboard

Type: workflow

A daily automated workflow that queries the clearinghouse for claim status updates (277CA responses and 835 ERA responses), identifies rejected or denied claims, categorizes them by denial reason code, and sends a morning summary report to the billing team. This is a deterministic monitoring workflow that runs on a scheduled timer, not triggered by user action.

Implementation:

Claims Rejection Monitor — Daily Scheduled Workflow
plaintext
# Claims Rejection Monitor — Daily Scheduled Workflow
# Platform: Microsoft Power Automate (if using M365) or Keragon
# Schedule: Daily at 7:00 AM local time

Power Automate Flow Definition

Trigger

  • Type: Recurrence
  • Frequency: Day
  • Interval: 1
  • Start time: 07:00 AM
  • Time zone: Practice local time

Flow Steps

Step 1: Query Clearinghouse for Claim Status Updates

Stedi API request to retrieve rejected and denied claim responses
http
GET https://healthcare.stedi.com/2024-04-01/claim-responses
Headers:
  Authorization: Bearer {STEDI_API_KEY}
Query Parameters:
  updatedSince: {yesterday_date}T00:00:00Z
  status: rejected,denied

Step 2: Parse and Categorize Rejections

Parse rejected claims and categorize by denial reason code
javascript
// Categorize by denial reason for actionable reporting
const categories = {
  'eligibility': [],      // Patient not covered
  'authorization': [],    // Missing prior auth
  'coding': [],           // CPT/ICD mismatch, modifier issues
  'data_quality': [],     // Missing or invalid data fields
  'timely_filing': [],    // Past filing deadline
  'duplicate': [],        // Duplicate claim
  'other': []
};

const reasonCodeMap = {
  '1': 'eligibility',    // Deductible
  '2': 'eligibility',    // Coinsurance
  '3': 'eligibility',    // Co-pay
  '4': 'coding',         // Procedure code inconsistent with modifier
  '16': 'data_quality',  // Claim lacks information
  '18': 'duplicate',     // Exact duplicate
  '27': 'coding',        // Expenses not covered
  '29': 'timely_filing', // Filing limit exceeded
  '50': 'data_quality',  // Non-covered service
  '96': 'data_quality',  // Non-covered charge
  '197': 'authorization' // Precertification required
};

rejectedClaims.forEach(claim => {
  const category = reasonCodeMap[claim.reasonCode] || 'other';
  categories[category].push({
    claimId: claim.id,
    patientName: claim.patientName,
    dateOfService: claim.serviceDate,
    cptCode: claim.procedureCode,
    payerName: claim.payerName,
    reasonCode: claim.reasonCode,
    reasonDescription: claim.reasonDescription,
    amount: claim.chargeAmount
  });
});

Step 3: Generate HTML Email Report

Handlebars-templated HTML email body for daily claims status report
html
<h2>Daily Claims Status Report — {{currentDate}}</h2>

<h3>Summary</h3>
<table border='1' cellpadding='5'>
  <tr><td><strong>Total Claims Submitted (yesterday)</strong></td><td>{{totalSubmitted}}</td></tr>
  <tr><td><strong>Accepted</strong></td><td style='color:green'>{{totalAccepted}}</td></tr>
  <tr><td><strong>Rejected/Denied</strong></td><td style='color:red'>{{totalRejected}}</td></tr>
  <tr><td><strong>Clean Claim Rate</strong></td><td>{{cleanClaimRate}}%</td></tr>
  <tr><td><strong>Total $ at Risk (rejected)</strong></td><td>${{totalAtRisk}}</td></tr>
</table>

<h3>Rejected Claims Requiring Action</h3>
{{#each categories}}
  {{#if this.length}}
  <h4>{{@key}} ({{this.length}} claims)</h4>
  <table border='1' cellpadding='5'>
    <tr><th>Patient</th><th>DOS</th><th>CPT</th><th>Payer</th><th>Reason</th><th>Amount</th></tr>
    {{#each this}}
    <tr>
      <td>{{patientName}}</td>
      <td>{{dateOfService}}</td>
      <td>{{cptCode}}</td>
      <td>{{payerName}}</td>
      <td>{{reasonCode}}: {{reasonDescription}}</td>
      <td>${{amount}}</td>
    </tr>
    {{/each}}
  </table>
  {{/if}}
{{/each}}

<h3>Action Required</h3>
<ul>
  <li><strong>Coding errors:</strong> Review and correct CPT/ICD codes, resubmit within 24 hours</li>
  <li><strong>Data quality:</strong> Check patient demographics and insurance info, correct and resubmit</li>
  <li><strong>Authorization:</strong> Contact payer for retroactive authorization or appeal</li>
  <li><strong>Eligibility:</strong> Verify patient coverage, consider secondary insurance or patient billing</li>
</ul>

Step 4: Send Report

  • To: billing@practice.com, practiceowner@practice.com
  • CC: msp-healthcare-team@mspcompany.com (for MSP monitoring)
  • Subject: "Daily Claims Report — {{currentDate}} — {{totalRejected}} claims need attention"
Build monthly metrics object and write to SharePoint list via Power Automate
javascript
// Append to SharePoint list or Excel Online for monthly KPI tracking
const monthlyMetrics = {
  date: currentDate,
  totalSubmitted: totalSubmitted,
  totalAccepted: totalAccepted,
  totalRejected: totalRejected,
  cleanClaimRate: cleanClaimRate,
  totalRevenueSubmitted: totalRevenue,
  totalRevenueAtRisk: totalAtRisk,
  topDenialReason: getTopDenialReason(categories)
};
// Write to SharePoint list via Power Automate connector

KPI Targets

  • Clean claim rate: >95% (industry benchmark)
  • Average days to claim submission: <1 day (from session completion)
  • Denial follow-up time: <48 hours
  • Timely filing denials: 0 (auto-submission eliminates this category)

Superbill Field Completeness Validator

Type: integration

A real-time validation layer that runs within the EHR's note-signing workflow (or as a middleware check in Path B). Before any claim is generated, this validator checks that all 14 required superbill fields are present and correctly formatted. It prevents incomplete or malformed claims from entering the submission pipeline, directly improving the practice's clean claim rate.

Implementation:

Superbill Field Completeness Validator
plaintext
# validation step before claim generation

# Superbill Field Completeness Validator
# This runs as a validation step BEFORE claim generation
# Implementation: JavaScript function in Keragon, or Power Automate expression set

Required Fields Specification (ANSI X12 837P)

ANSI X12 837P required fields with validation rules
javascript
const REQUIRED_FIELDS = [
  {
    field: 'provider.npi',
    label: 'Rendering Provider NPI',
    validate: (val) => /^\d{10}$/.test(val),
    errorMsg: 'NPI must be exactly 10 digits'
  },
  {
    field: 'provider.taxId',
    label: 'Provider/Practice Tax ID (EIN)',
    validate: (val) => /^\d{9}$/.test(val?.replace('-', '')),
    errorMsg: 'Tax ID must be 9 digits (EIN format)'
  },
  {
    field: 'provider.taxonomyCode',
    label: 'Provider Taxonomy Code',
    validate: (val) => /^\d{10}X$/.test(val),
    errorMsg: 'Taxonomy code must be 10 digits followed by X'
  },
  {
    field: 'patient.firstName',
    label: 'Patient First Name',
    validate: (val) => val && val.trim().length > 0,
    errorMsg: 'Patient first name is required'
  },
  {
    field: 'patient.lastName',
    label: 'Patient Last Name',
    validate: (val) => val && val.trim().length > 0,
    errorMsg: 'Patient last name is required'
  },
  {
    field: 'patient.dateOfBirth',
    label: 'Patient Date of Birth',
    validate: (val) => /^\d{4}-\d{2}-\d{2}$/.test(val) && new Date(val) < new Date(),
    errorMsg: 'Valid DOB in YYYY-MM-DD format required'
  },
  {
    field: 'insurance.memberId',
    label: 'Insurance Member ID',
    validate: (val) => val && val.trim().length >= 3,
    errorMsg: 'Insurance member ID is required'
  },
  {
    field: 'insurance.payerId',
    label: 'Payer ID',
    validate: (val) => val && val.trim().length >= 3,
    errorMsg: 'Payer ID is required for electronic submission'
  },
  {
    field: 'service.dateOfService',
    label: 'Date of Service',
    validate: (val) => /^\d{4}-\d{2}-\d{2}$/.test(val),
    errorMsg: 'Date of service in YYYY-MM-DD format required'
  },
  {
    field: 'service.cptCode',
    label: 'CPT Procedure Code',
    validate: (val) => /^\d{5}$/.test(val),
    errorMsg: 'CPT code must be exactly 5 digits'
  },
  {
    field: 'service.diagnosisCodes',
    label: 'ICD-10 Diagnosis Code(s)',
    validate: (val) => {
      if (!Array.isArray(val) || val.length === 0) return false;
      // Check specificity: minimum 4 characters (letter + 2 digits + dot or more)
      return val.every(code => /^[A-Z]\d{2}\.\d{1,4}$/.test(code));
    },
    errorMsg: 'At least one ICD-10 code required, coded to highest specificity (e.g., F41.1, not F41)'
  },
  {
    field: 'service.placeOfService',
    label: 'Place of Service Code',
    validate: (val) => ['02', '10', '11', '12', '20', '22', '49', '50', '53', '71', '72'].includes(val),
    errorMsg: 'Valid place of service code required (common: 11=Office, 10=Telehealth Home, 02=Telehealth Facility)'
  },
  {
    field: 'service.chargeAmount',
    label: 'Charge Amount',
    validate: (val) => typeof val === 'number' && val > 0,
    errorMsg: 'Charge amount must be a positive number'
  },
  {
    field: 'service.units',
    label: 'Service Units',
    validate: (val) => Number.isInteger(val) && val > 0,
    errorMsg: 'Service units must be a positive integer (typically 1 for psychotherapy)'
  }
];
CPT-to-duration cross-validation rules
javascript
// CPT-to-duration cross-validation
const CPT_DURATION_RULES = {
  '90832': { min: 16, max: 37, label: '16-37 minute psychotherapy' },
  '90834': { min: 38, max: 52, label: '38-52 minute psychotherapy' },
  '90837': { min: 53, max: 120, label: '53+ minute psychotherapy' }
};
Telehealth modifier validation rules
javascript
// Telehealth modifier validation
const TELEHEALTH_RULES = {
  '10': { requiredModifiers: ['95', '93', 'FQ', 'GT'], label: 'Telehealth POS requires telehealth modifier' },
  '02': { requiredModifiers: ['95', '93', 'FQ', 'GT'], label: 'Telehealth POS requires telehealth modifier' },
  '11': { forbiddenModifiers: ['95', '93', 'FQ'], label: 'In-office POS should not have telehealth modifier' }
};
Superbill validation function with CPT-duration and telehealth modifier checks
javascript
function validateSuperbill(claimData) {
  const results = { valid: true, errors: [], warnings: [] };
  
  // Check each required field
  REQUIRED_FIELDS.forEach(rule => {
    const value = getNestedValue(claimData, rule.field);
    if (!rule.validate(value)) {
      results.valid = false;
      results.errors.push({ field: rule.field, label: rule.label, message: rule.errorMsg, value: value });
    }
  });
  
  // CPT-duration cross-validation
  const cpt = claimData.service?.cptCode;
  const duration = claimData.service?.duration;
  if (CPT_DURATION_RULES[cpt] && duration) {
    const rule = CPT_DURATION_RULES[cpt];
    if (duration < rule.min || duration > rule.max) {
      results.warnings.push({
        field: 'service.duration',
        message: `Duration ${duration}min may not match CPT ${cpt} (${rule.label}). Verify documentation.`
      });
    }
  }
  
  // Telehealth modifier validation
  const pos = claimData.service?.placeOfService;
  const modifiers = claimData.service?.modifiers || [];
  if (TELEHEALTH_RULES[pos]) {
    const rule = TELEHEALTH_RULES[pos];
    if (rule.requiredModifiers) {
      const hasRequired = modifiers.some(m => rule.requiredModifiers.includes(m));
      if (!hasRequired) {
        results.valid = false;
        results.errors.push({ field: 'service.modifiers', message: rule.label });
      }
    }
    if (rule.forbiddenModifiers) {
      const hasForbidden = modifiers.some(m => rule.forbiddenModifiers.includes(m));
      if (hasForbidden) {
        results.warnings.push({ field: 'service.modifiers', message: rule.label });
      }
    }
  }
  
  return results;
}

// Helper: access nested object properties by dot-notation path
function getNestedValue(obj, path) {
  return path.split('.').reduce((current, key) => current?.[key], obj);
}

Integration Points

  • Path A (Built-in EHR): This logic is handled natively by the EHR's claim validation engine. The MSP's role is to configure required fields in the EHR note templates so clinicians cannot sign incomplete notes.
  • Path B (Custom API): Deploy this as a Keragon function node between the Healthie data extraction step and the Stedi claim submission step. Claims that fail validation are routed to an error queue and the billing team is alerted immediately.

Payer Enrollment Status Tracker

Type: workflow

A tracking workflow that monitors the status of ERA/EFT enrollment for each insurance payer. During initial setup, payer enrollments are the longest lead-time item (2–6 weeks per payer). This tracker ensures no payer falls through the cracks and provides visibility to both the MSP team and the practice's billing staff.

Implementation

1
Payer Enrollment Status Tracker
2
Platform: SharePoint List + Power Automate (included in M365 Business Premium)

SharePoint List Schema: 'Payer Enrollment Tracker'

PayerName

Single line of text

e.g., 'Aetna'

PayerID

Single line of text

e.g., '60054'

EnrollmentType

Choice

'837P Claims', 'ERA/835', 'EFT', 'Real-Time Eligibility'

SubmissionDate

Date

Date enrollment request was submitted

ExpectedCompletionDate

Date

SubmissionDate + 30 days (default)

Status

Choice

'Not Started', 'Submitted', 'Pending Payer', 'Active', 'Rejected', 'Resubmitted'

Notes

Multi-line text

Free-form notes

LastCheckedDate

Date

Date MSP last checked status

AssignedTo

Person

MSP technician or billing staff member

Power Automate Flow: Weekly Enrollment Status Check Reminder

Trigger: Recurrence — Every Monday at 8:00 AM

Steps:

1
Get items from SharePoint list where Status != 'Active'
2
Filter array for items where ExpectedCompletionDate < Today (overdue)
3
Send email to AssignedTo with list of overdue enrollments
4
Send summary email to practice billing manager with all pending enrollments

Email Template:

Subject: Payer Enrollment Status Update — {{count}} enrollments pending

The following payer enrollments are still pending:

Handlebars template loop for pending enrollments
handlebars
{{#each pendingEnrollments}}
- {{PayerName}} ({{EnrollmentType}}): Submitted {{SubmissionDate}}, Status: {{Status}}
  {{#if overdue}}⚠️ OVERDUE — expected by {{ExpectedCompletionDate}}{{/if}}
{{/each}}

Action Required:

  • Check clearinghouse portal for status updates
  • Contact payer enrollment department for overdue items
  • Update tracker with current status

Initial Population Script (run once during setup)

Initial population script
javascript
// run once during setup to pre-populate the payer enrollment tracker

// Common payers for mental health practices — pre-populate the tracker
const commonPayers = [
  { name: 'Aetna', id: '60054' },
  { name: 'Anthem BCBS', id: '00230' },
  { name: 'Cigna / Evernorth', id: '62308' },
  { name: 'UnitedHealthcare / Optum', id: '87726' },
  { name: 'Humana', id: '61101' },
  { name: 'Magellan Health', id: '77027' },
  { name: 'Medicare (local MAC)', id: 'VARIES' },
  { name: 'Medicaid (state MCO)', id: 'VARIES' },
  { name: 'Tricare', id: '99726' },
  { name: 'Beacon Health Options', id: '13551' }
];

// For each payer, create 3 enrollment rows: 837P, ERA, EFT
const enrollmentTypes = ['837P Claims', 'ERA/835', 'EFT'];
const rows = [];
commonPayers.forEach(payer => {
  enrollmentTypes.forEach(type => {
    rows.push({
      PayerName: payer.name,
      PayerID: payer.id,
      EnrollmentType: type,
      Status: 'Not Started',
      SubmissionDate: null,
      ExpectedCompletionDate: null,
      Notes: '',
      AssignedTo: 'MSP Technician'
    });
  });
});
// Bulk insert into SharePoint list via Power Automate or PnP PowerShell

Testing & Validation

  • NETWORK TEST: From a clinical workstation, run 'Test-NetConnection simplepractice.com -Port 443' (or the practice's EHR domain) and verify TLS 1.2+ connection. Repeat for the clearinghouse portal. All connections must show TcpTestSucceeded: True.
  • ENCRYPTION TEST: Run 'manage-bde -status C:' on every Windows workstation and verify 'Protection Status: Protection On' and 'Encryption Method: XTS-AES 256'. On macOS, run 'fdesetup status' and verify FileVault is On.
  • MFA TEST: Log out of the EHR and Microsoft 365 on a workstation. Log back in and verify that MFA is prompted (Authenticator app, not SMS). Repeat for each user account. Accounts that bypass MFA represent a compliance failure.
  • FIREWALL TEST: Attempt to access a known-blocked website category (e.g., gambling) from the clinical VLAN and verify it is blocked. Then verify that the EHR, clearinghouse, and M365 portals are accessible. Test guest VLAN isolation by attempting to ping a clinical workstation from a guest-connected device — it should fail.
  • SUPERBILL AUTO-GENERATION TEST: Have a clinician complete a test session note with all required fields (CPT 90834, ICD-10 F41.1, POS 11, start/stop times). Sign the note. Verify that a superbill/claim is automatically generated in the EHR's billing queue within 5 minutes of signing.
  • INCOMPLETE NOTE REJECTION TEST: Have a clinician attempt to sign a note with a missing diagnosis code (or missing start/stop time for a time-based CPT code). Verify that the EHR prevents the note from being signed or flags the claim for review rather than auto-submitting it.
  • TELEHEALTH MODIFIER TEST: Create a test session noted as telehealth (video). Verify the claim auto-populates with POS 10 and Modifier 95. Then create an audio-only session and verify POS 10 with Modifier FQ. Submit both through the clearinghouse and monitor for acceptance.
  • CLEARINGHOUSE RECEIPT TEST: After submitting a test claim, log into the clearinghouse portal within 4 hours and verify the claim appears with status 'Accepted' or 'Forwarded to Payer'. If status shows 'Rejected', review the error code and correct the issue.
  • ERA AUTO-POSTING TEST: After a test claim has been adjudicated by the payer (typically 7–21 days), verify that the ERA (835) is received by the clearinghouse and auto-posted to the patient's account in the EHR. The payment amount, patient responsibility (co-pay, deductible), and adjustment codes should all populate correctly.
  • 42 CFR PART 2 CONSENT TEST: If the practice treats SUD patients, create a test claim with an F10-F19 diagnosis code for a patient WITHOUT a documented TPO consent. Verify the system holds the claim for manual review and alerts the billing team, rather than auto-submitting.
  • END-TO-END TIMING TEST: Time the complete workflow from note signing to claim appearing in the clearinghouse queue. Target: less than 15 minutes for real-time systems (SimplePractice, TheraPlatform) or by end of same business day for batch systems (TherapyNotes).
  • DENIAL ALERT TEST: If possible, submit a claim with a known issue (e.g., to a payer where the provider is not yet credentialed). Verify that the denial notification is received by billing staff via email within 24 hours of the clearinghouse processing the rejection.
  • BACKUP RECOVERY TEST: Verify that Datto/Axcient backup is running successfully on all protected endpoints. Perform a test file restore to confirm backup integrity. Review backup logs in the RMM dashboard.
  • HUNTRESS EDR TEST: Verify the Huntress agent is installed and reporting on all endpoints by checking the Huntress dashboard. Confirm the practice's endpoints appear in the correct organization. Run an EICAR test file to verify detection capability.

Client Handoff

The client handoff meeting should be a 90-minute session with both clinical leadership and billing/administrative staff present. Cover the following topics in order:

1
System Overview (15 min): Walk through the complete automation chain: session → note signed → superbill auto-generated → claim queued → clearinghouse → payer → ERA → payment posted. Show this flow on the actual system using a real recent example.
2
Clinician Responsibilities (20 min): Demonstrate the note-signing workflow. Emphasize that note completeness now directly impacts revenue — missing fields prevent claims from submitting. Review the CPT code quick-reference card and ICD-10 favorites list. Demonstrate the telehealth modifier selection. Review the 24-hour note signing target.
3
Billing Staff Daily Routine (20 min): Walk through the daily claim queue review process (should take 5–10 minutes each morning). Show how to identify and resolve flagged claims. Demonstrate the denial notification workflow. Show the ERA auto-posting verification process. Review the daily claims status report email.
4
Exception Handling (15 min): What to do when a claim is rejected (step-by-step flowchart). How to submit corrected claims. How to handle secondary insurance claims. When to escalate to the MSP vs. handle internally.
5
Documentation Handoff (10 min): Leave the following documents with the practice: (a) Laminated CPT/ICD-10 quick-reference cards for each clinician, (b) 1-page 'Claim Denied — What To Do' flowchart, (c) Payer enrollment status tracker (SharePoint link), (d) Network diagram with IP addresses and admin portal URLs, (e) HIPAA Security Risk Assessment report, (f) BAA inventory spreadsheet, (g) Emergency contact card for MSP support.
6
Success Criteria Review (10 min): Review the baseline metrics captured during assessment and set targets: clean claim rate >95%, average days to submission <1, timely filing denials = 0, ERA auto-posting success rate >90%. Schedule a 30-day follow-up review to measure against these targets.
7
Support & Escalation (5 min): Confirm the MSP support process: how to submit tickets, expected response times, after-hours emergency contact. Remind the practice that the MSP monitors the claims rejection dashboard and will proactively alert on systemic issues.

Maintenance

Ongoing MSP maintenance responsibilities for this implementation:

Weekly (15–30 minutes):

  • Review the automated Claims Rejection Monitor report. Look for patterns: if a specific payer is rejecting >10% of claims, investigate whether it's a systemic issue (enrollment problem, code change, payer policy update).
  • Check firewall logs and Huntress dashboard for security alerts on practice endpoints.
  • Verify backup jobs completed successfully for all protected devices.

Monthly (1–2 hours):

  • Review clean claim rate trending in the SharePoint KPI tracker. Target: >95% clean claim rate sustained month-over-month.
  • Check for EHR platform updates or feature changes that could affect the auto-claim workflow. Apply any updates during a maintenance window.
  • Review and rotate any service account passwords or API keys used in the automation workflows.
  • Update FortiGate firmware and FortiGuard definitions if not set to auto-update.
  • Verify all payer enrollments remain active — some payers require annual re-enrollment.
  • Review Microsoft 365 Secure Score and address any new recommendations.

Quarterly (2–4 hours):

  • Conduct a mini HIPAA compliance review: verify BAAs are current, review access logs for terminated employees, confirm encryption status on all endpoints, test backup restore procedures.
  • Review CPT and ICD-10 code updates — CMS publishes annual updates effective January 1 and occasional mid-year corrections. Update EHR templates if codes change.
  • Review payer fee schedule changes and update charge amounts in the EHR if applicable.
  • Meet with practice billing manager to review revenue cycle KPIs and identify improvement opportunities.

Annually:

  • Conduct full HIPAA Security Risk Assessment (billable engagement at $1,500–$5,000).
  • Review and renew all BAAs.
  • Review and update the practice's HIPAA policies and procedures.
  • Conduct HIPAA workforce training for all staff (new and existing).
  • Review firewall and endpoint hardware for end-of-life/end-of-support status.
  • Review and renew FortiGuard, Huntress, backup, and M365 licenses.
  • Verify that the practice's NPI and CAQH records are current.

Escalation Path:

  • Tier 1 (billing staff): Handles individual claim corrections, patient insurance updates, ERA posting discrepancies.
  • Tier 2 (MSP helpdesk): Handles EHR configuration issues, clearinghouse connectivity problems, payer enrollment issues, workstation/network problems.
  • Tier 3 (MSP engineer): Handles firewall changes, API integration issues (Path B), security incidents, HIPAA breach response.
  • Vendor escalation: EHR vendor support for platform bugs; clearinghouse support for EDI transmission failures; payer provider relations for enrollment issues.

SLA Considerations:

  • Claims submission failures: 4-hour response during business hours (revenue impact).
  • Security incidents: 1-hour response (HIPAA breach potential).
  • Network/connectivity outages: 2-hour response (blocks all cloud-based workflows).
  • Routine configuration requests: Next business day.

Alternatives

Path A: Native EHR Auto-Claim (Built-in Features Only)

Use the EHR's built-in auto-claim generation and clearinghouse submission without any external middleware, API integration, or custom development. The MSP configures the EHR's billing settings, enrolls payers, trains staff, and monitors results. This covers SimplePractice, TherapyNotes, TheraPlatform, Sessions Health, and Kalix — all of which have built-in claim submission pipelines.

Note

Tradeoffs: LOWEST cost ($0 additional beyond EHR subscription), LOWEST complexity (configuration only, no code), FASTEST implementation (4–6 weeks). However, limited customization — you're constrained to whatever automation the EHR vendor has built. No external webhook triggers, no custom validation logic, no cross-platform data flow. If the EHR's built-in workflow has a gap (e.g., no auto-submit, only auto-generate-and-queue), the workaround is manual batch submission by billing staff (adds 5–10 minutes/day). RECOMMENDED for 80% of practices — solo practitioners and small groups with straightforward billing workflows.

Path B: Healthie API + Keragon + Stedi (Custom Integration)

Use Healthie's GraphQL API and webhooks to detect session completion, Keragon as HIPAA-compliant middleware to orchestrate the workflow, and Stedi's Claims API for clearinghouse submission. This provides fully automated, real-time claim submission with custom validation, 42 CFR Part 2 consent checks, and rich error handling — all configurable by the MSP.

Note

Tradeoffs: HIGHER cost (~$250–$500/month additional for Keragon + Stedi beyond the EHR), MODERATE-HIGH complexity (requires developer resources for initial build, 8–12 weeks timeline), but provides MAXIMUM flexibility and automation. Custom validation logic can catch errors that built-in EHR validation misses. Supports complex multi-site, multi-provider, multi-payer scenarios. Best for practices with 5+ providers, high claim volume (200+ claims/month), or specific customization requirements. Requires ongoing developer maintenance for API version updates.

Path C: Microsoft Power Automate + EHR Manual Export

For EHRs without APIs or built-in auto-claim (legacy systems), use Power Automate to monitor a shared folder or email inbox for exported superbill files, parse them, and route them to a clearinghouse portal via browser automation (UI flows) or API. This is a bridge solution for practices that cannot or will not switch EHRs.

Warning

Tradeoffs: MODERATE cost (Power Automate included in M365 Business Premium; may need Premium connectors at $15/user/month), HIGH complexity and FRAGILITY (UI-based automation breaks when the clearinghouse portal UI changes), MODERATE implementation time (6–8 weeks). This approach is inherently brittle — it relies on screen scraping or file watching rather than proper API integration. Recommended ONLY as a temporary bridge while the practice plans an EHR migration. Not suitable as a permanent solution.

Path D: Outsourced Medical Billing Service

Instead of automating in-house billing, the practice outsources claims submission to a third-party medical billing company (e.g., Practice Solutions, Therapy Brands billing services, or a local billing company). The clinicians complete notes, and the billing company handles superbill generation, claim submission, denial management, and payment posting.

Tradeoffs

  • ZERO technology implementation required from the MSP (no middleware, no API work).
  • The MSP's role is limited to providing the secure network and HIPAA compliance infrastructure.
  • Cost is typically 5–10% of collections (for a practice collecting $500K/year, that's $25K–$50K/year in billing fees).
  • MORE expensive than in-house automation long-term but LOWER risk and FASTER to implement (can be operational in 1–2 weeks once the billing company is contracted).
  • Best for practices that have no billing staff and no desire to manage billing internally.
  • The MSP loses the claims monitoring and EHR administration managed service revenue opportunity but retains all security/compliance/infrastructure revenue.

Alternative EHR: TheraPlatform with Built-in Auto-Claim

TheraPlatform offers the lowest entry price ($39/month Basic, $69/month Pro) with built-in claim automation that can be configured to automatically generate and submit claims after session notes are signed. It includes telehealth, scheduling, and documentation in addition to billing. Good alternative for extremely budget-conscious practices.

Note

Tradeoffs: LOWEST EHR cost in the market for auto-claim capability. $0.25/claim transaction fee (same as SimplePractice). However, smaller user community means fewer online resources and peer support. Less polished UX compared to SimplePractice. Limited third-party integrations. No public API for custom builds. Recommended when budget is the primary constraint and the practice needs basic auto-claim without customization.

Want early access to the full toolkit?