Candid | Canvas Medical

mbiannaccone/candid

Candid Health Integration

Canvas plugin that replaces the built-in candid_integration/ module. Handles the full claim lifecycle with Candid Health: submission, adjudication sync, patient payment reporting and syncing, and claim status banners.

ID Glossary

Three IDs flow through this plugin and can be confused if you're not careful:

ID Source Format Where it lives
Canvas claim ID Canvas UUID (00000000-...) claim.id, used for ClaimEffect, metadata lookups, and as external_id sent to Candid (canvas:{claim_id})
Candid encounter ID Candid returns it from POST /encounters/v4 UUID Stored in claim metadata as candid_encounters[].candid_encounter_id. Used to GET /encounters/v4/{id}
Candid claim ID Nested inside the encounter response at claims[].claim_id Candid-generated Not stored. Discovered at sync time, used ephemerally to query GET /patient-payments/v4?claim_id={id}

In code, variables are prefixed to avoid ambiguity:

The Candid encounter response has this hierarchy:

Encounter (encounter_id)            <- we create via POST, fetch via GET
  +-- claims[]
        +-- claim_id               <- Candid creates internally
        +-- status                 <- e.g. "era_received", "finalized_paid"
        +-- eras[]                 <- ERA IDs for dedup
        |     +-- era_id
        |     +-- check_number
        |     +-- check_date
        +-- service_lines[]        <- payment/adjustment amounts per line
              +-- procedure_code
              +-- charge_amount_cents
              +-- primary_paid_amount_cents
              +-- allowed_amount_cents
              +-- deductible_cents, coinsurance_cents, copay_cents
              +-- service_line_era_data.service_line_adjustments[]
              +-- service_line_manual_adjustments[]

Features

Claim Submission

When a claim is moved to the QueuedForSubmission queue, the plugin schedules a submission to Candid's /api/encounters/v4 endpoint via the plugin's own /submit SimpleAPI route after a 60-second grace period (GRACE_PERIOD_SECONDS). When the submit handler fires, it re-checks the claim's queue and skips the submission if the user has moved the claim out of QueuedForSubmission during the grace window.

Adjudication Sync (Pull-Based)

Candid has no webhooks, so the plugin pulls adjudication data via GET /api/encounters/v4/{encounter_id} and patient payments via GET /api/patient-payments/v4?claim_id={candid_claim_id}.

Triggers:

A separate one-time midnight cron migrates legacy SyncLog rows into candid_sync_history metadata.

Insurance adjudication (ERA data):