Canvas Medical Plugins
How YourEra extends Canvas to integrate with the rest of our backend. Now org-aware under the Canvas-first architecture.
Canvas plugins are becoming organization-aware: prescription_queue now respects Patient.managingOrganization for routing (tasks go to physicians with active physician_org_memberships for that org), and portal_invite pulls the org-branded template for welcome flows. We use Canvas Plugin Assistant (CPA) — Canvas Medical's own Claude Code plugin — for plugin development (installed; run export CPA_RUNNING=1 && export CPA_WORKSPACE_DIR=$(pwd) before starting Claude to activate). CPA provides deep SDK knowledge, scaffolding, test generation, and deploy commands. See Architecture Overview for how the plugin layer fits into Canvas-first.
Why we have plugins at all
Canvas Medical is YourEra's EMR of record for DTC post-migration. Canvas exposes two integration surfaces, and we use both:
We use FHIR for CRUD operations (create a Patient, update an encounter, attach a note). It's HTTP with bearer auth, and every service that needs to read or write Canvas data uses the FHIR client library directly.
We use SDK plugins for anything reactive: hooking into Canvas events (e.g., a prescription was signed, a patient was created), or rendering custom UI inside Canvas itself (physician queue, patient portal widgets). Plugins are Python, written against Canvas's SDK, and installed with canvas install ..
Plugin inventory
Five plugins across four repos. Grouped by type:
| Plugin | Type | Trigger | What it does |
|---|---|---|---|
prescription_webhook |
Webhook | PRESCRIPTION_CREATEDPRESCRIPTION_UPDATED |
POSTs signed-Rx data to api.hisera.com/api/webhooks/canvas/prescription. The critical trigger that kicks off fulfillment. |
portal_invite |
Lifecycle | PATIENT_CREATED |
Sends YourEra-branded welcome email via integration service. Sets Canvas User's portal email (required for Canvas messaging to work). |
message_notify |
Webhook | Canvas message events | Notifies patients outside Canvas when Canvas messages are sent. Routes through notification-service. |
prescription_queue |
In-Canvas UI | Physician UI | Canvas-side workflow for reviewing pending Rx's. Custom view for physicians to triage and sign. |
canvas-portal-customization |
In-Canvas UI | Patient portal page load | Custom widgets on Canvas's patient portal landing page (welcome banner, quick actions, appointments, health tips). Note: our main patient portal is at patient.yourera.com; this is for users who land on Canvas's own portal. |
Event flow master diagram
Every plugin in one picture: trigger on the left, plugin in the middle, destination on the right.
Critical path: prescription_webhook
This is the single most important plugin. It's the trigger that starts the entire fulfillment pipeline. If it doesn't fire, or fires with a bad payload, nothing downstream runs.
Webhook payload shape
Fields the plugin assembles and POSTs (see canvas-prescription-webhook-plugin/prescription_webhook/README.md):
event_type-PRESCRIPTION_CREATEDorPRESCRIPTION_UPDATEDtimestamppatient_idand fullpatientblock (name, DOB, gender)medication_request_idand fullmedication_request(med, sig, quantity, days supply, refills)prescriber(name, NPI)context.note_id
Authentication is a static shared secret (WEBHOOK_SECRET) configured in Canvas per environment. The integration service rejects any payload whose header doesn't match.
What breaks this
- Canvas OAuth creds missing in the plugin - the SDK can't fetch patient data. Plugin throws, no webhook fires.
- WEBHOOK_SECRET mismatch between Canvas config and integration service env - plugin fires, but integration service returns 401 and pipeline never starts.
- Integration service unreachable (deploy in progress, network) - plugin hits a timeout. Canvas doesn't retry. Patient is stuck in "signed but not fulfilled" until we reconcile manually.
- Canvas SDK version mismatch - the
CANVAS_MANIFEST.jsondeclares which SDK version the plugin expects. If Canvas upgrades without us upgrading the plugin, the event shape can shift.
Individual plugin reference
canvas-prescription-webhook-plugin/prescription_webhook/The fulfillment trigger. Fires whenever a prescription is created or updated in Canvas and sends the full record to the integration service, which kicks off the orchestrator pipeline.
PRESCRIPTION_CREATED, PRESCRIPTION_UPDATEDapi.hisera.com/api/webhooks/canvas/prescriptionWEBHOOK_SECRET header (static shared secret)canvas-plugins/portal_invite/Fixes a gap in Canvas: when a patient is created via FHIR (from intake), the email goes into the FHIR Patient.telecom array, but Canvas keeps a separate email field on the CanvasUser record for portal access. Without this plugin, that second field is blank and portal invites fail.
PATIENT_CREATED- Extract email from FHIR
Patient.telecom - POST welcome email request to integration service (
/api/send-welcome-email) - Set the email on Canvas User via
UpdateUserEffect
patient.yourera.com with OTP login instead.canvas-plugins/message_notify/Pushes a notification to the patient (outside Canvas) whenever a clinical message is sent to them inside Canvas. Otherwise the patient would only see the message if they happened to open our portal.
canvas-plugins/prescription_queue/Physician-facing queue inside Canvas: pending Rx's waiting for review, sorted by urgency. Lets providers triage and sign without leaving Canvas.
canvas-portal-customization/Customizes Canvas's patient portal landing with branded widgets: welcome banner, quick actions (schedule, message, refill), appointments, health tips, contact info. Most patients use patient.yourera.com, but anyone who lands on Canvas's native portal sees YourEra branding instead of Canvas defaults.
Plugin deployment
Every plugin has a CANVAS_MANIFEST.json that declares its protocols, permissions, and SDK version. Deployment is a two-step process:
- Install the plugin code to a Canvas instance:
cd <plugin-repo> canvas install . - Configure secrets in Canvas Settings → Plugins. Each plugin declares what secrets it needs (e.g.,
prescription_webhookneedsWEBHOOK_URLandWEBHOOK_SECRET).
Canvas credentials for install live in ~/.canvas/credentials.ini. Dev uses a different Canvas instance from prod; keep the credentials scoped.
Viewing logs
canvas logs --host <instance-name>
Related docs
- Architecture Overview - where plugins sit in the full system diagram
- Integration Service - webhook receiver for
prescription_webhookand destination forportal_invite's welcome email - Prescription Orchestrator - the 7-step pipeline that runs after
prescription_webhookfires - Notification Service - destination for
message_notify - Migration Status - current state of the Bask → Canvas migration