Patient Portal
White-labeled patient-facing app. Hostname-based org resolution, Clerk auth with org-scoped sessions, live canvas-client reads. Stateless — no DB.
The patient portal is now white-labeled per organization and runs at {slug}.portal.yourera.com + client custom domains (e.g., patients.sleepcorner.com, provisioned via organization_custom_domains). Middleware resolves hostname → org, injects branding + portal config, scopes the Clerk session's organization_id claim. No DB, no cache — every clinical read goes live to Canvas via @yourera/canvas-client. Per-org feature flags (patient_portal_show_messaging, show_refill_controls, show_appointment_scheduling) drive route visibility. Self-initiated actions: address update (writes Canvas Patient), payment method, marketing opt-out, pause/cancel subscription (default cancel_at_period_end). See Architecture Overview for the current spec. Content below may reflect the pre-redesign DTC-only portal.
Overview
The Patient Portal is an Express 5 BFF (Backend for Frontend) serving a React SPA at
patient.hisera.com. It runs on ECS behind the ALB at priority 5 and provides
patients with a self-service dashboard for managing their prescriptions, tracking shipments,
messaging their care team, and scheduling telehealth appointments.
The BFF layer handles authentication, session management, and proxies requests to both Canvas FHIR and the Integration Service, ensuring patients only see their own data.
Architecture
┌──────────────────────────────────────────┐
│ patient.hisera.com (ECS via ALB) │
│ Express 5 BFF + Vite React SPA │
└───────────┬──────────────┬───────────────┘
│ │
Canvas FHIR API Integration Service
│ │
├─ Patients ├─ Shipments
├─ MedicationReq ├─ Notifications
├─ Communications ├─ FedEx Tracking
└─ Appointments └─ Scheduling
Infrastructure
| Component | Value |
|---|---|
| Host | patient.hisera.com |
| ALB Priority | 5 (highest priority rule) |
| Target Group | hisera-tg-patient-portal |
| Runtime | ECS Fargate |
Authentication
The Patient Portal supports two authentication methods. Both result in a JWT session cookie with a 4-hour expiry.
Email OTP
Patients enter their email address and receive a verification code via SendGrid. The code is validated server-side and a JWT session cookie is set.
Sends a verification code to the patient's email address via SendGrid.
Validates the verification code and sets a JWT session cookie (4h expiry).
Canvas OAuth SSO
Patients who access the portal from a Canvas magic link are authenticated via Canvas OAuth. The BFF exchanges the authorization code for a Canvas access token, looks up the patient, and issues a JWT session cookie.
yourera.canvasmedical.com sends magic link emails
via the SendInviteEffect plugin. The magic link redirects to the patient portal
with an OAuth authorization code.
Dashboard
The dashboard is the patient's landing page after authentication. It displays a personalized greeting and a summary of their active treatment.
Dashboard Components
- Greeting - Personalized with the patient's first name from Canvas FHIR
- Active Subscriptions - Current medication, dosage, and next refill date
- Shipment Tracker - Most recent shipment status with tracking link
- Telehealth Status - Upcoming appointment or option to schedule
- Notifications - Unread notification count badge
Prescriptions
The Prescriptions page displays the patient's medication history from Canvas FHIR
MedicationRequest resources. Each prescription shows the medication name,
dosage, prescriber, status, and date written.
Returns the authenticated patient's prescriptions from Canvas FHIR MedicationRequest.
Prescription Fields
| Field | Source | Description |
|---|---|---|
medication |
MedicationRequest.medicationCodeableConcept | Medication name and code |
dosage |
MedicationRequest.dosageInstruction | Dosage and frequency instructions |
prescriber |
MedicationRequest.requester | Prescribing practitioner name and NPI |
status |
MedicationRequest.status | active, completed, cancelled, stopped |
dateWritten |
MedicationRequest.authoredOn | Date the prescription was written |
Shipments
The Shipments page shows all shipments for the patient, sourced from the Integration Service database. Each shipment includes real-time FedEx tracking data with a Leaflet map visualization.
Tracking Map
When a shipment has an active tracking number, the portal displays a Leaflet map (using CARTO Voyager tiles) with a pulsing location dot showing the package's last known location. The map is code-split into a ~47KB chunk and only loaded when needed.
Returns all shipments for the authenticated patient from the integration service database.
Proxies FedEx tracking data with ownership verification and geocoding. Results are cached for 30 minutes in the tracking_cache table.
Shipment Status Flow
label_created → packed → shipped → delivered
↘
voided
/api/patient/tracking/:trackingNumber endpoint verifies that the tracking
number belongs to the authenticated patient before returning data. This prevents patients
from viewing other patients' shipment details.
Messages
The Messages page provides a secure messaging interface between the patient and their
assigned care team. Messages are stored as Canvas FHIR Communication resources
and auto-polled every 15 seconds for new messages.
Features
- Auto-Polling - New messages checked every 15 seconds via
setInterval. The polling interval ensures near-real-time message updates without WebSocket complexity. - Practitioner Routing - Messages are routed to the patient's
generalPractitionerfrom their Canvas FHIR Patient resource. If no general practitioner is assigned, the system falls back to the default practitioner configured in the integration service. - Read Receipts - Messages are marked as read when viewed
- Thread View - Conversation-style chronological message display
Returns Canvas FHIR Communication resources for the authenticated patient.
Sends a new message to the patient's assigned practitioner via Canvas FHIR Communication.
Refill Management
The Subscriptions page allows patients to view and manage their recurring prescription refills. Each refill schedule shows the medication, next refill date, refills sent out of the total allowed, and current status. Patients have full self-service control over their refill schedules.
Patient Actions
- Pause - Temporarily suspend refills. Opens a modal where the patient can set an optional resume date. The schedule moves to
pausedstatus. - Resume - Reactivate a paused schedule via an inline button. The next refill date is recalculated from today.
- Delay - Push the next refill to a custom future date without pausing the schedule.
- Cancel - Permanently cancel remaining refills. Opens a confirmation modal. This action cannot be undone.
UI Components
- SubscriptionsPage - Main page listing all refill schedules with status badges and action buttons
- Pause Modal - Date picker for optional resume date
- Cancel Modal - Confirmation dialog with reason selection
- Status Display - Shows next refill date, refills sent/total count, and color-coded status badge
API Endpoints
Returns all refill schedules for the authenticated patient, including status, next fill date, refills sent/total, and medication name.
Pause a refill schedule. Accepts optional resumeDate in the request body.
Resume a paused refill schedule. Recalculates the next fill date from today.
Delay the next refill to a specific future date. Requires newDate in the request body.
Permanently cancel remaining refills. This action cannot be undone.
Scheduling
The Scheduling page allows patients to book, reschedule, and cancel telehealth appointments. The availability engine checks practitioner schedules and the Zoom integration creates meeting links automatically.
Booking Flow
- Patient selects a date from the availability calendar
- Available time slots are displayed based on practitioner schedules
- Patient selects a time slot and confirms the appointment
- A Zoom meeting is created automatically via the Zoom API
- Confirmation is sent to the patient via email and in-app notification
Returns available time slots for the given date from the availability engine.
Books a telehealth appointment. Creates a Zoom meeting and sends confirmation notifications.
Returns the patient's upcoming and past appointments with Zoom meeting links.
Notifications
The notification system provides persistent, database-backed notifications delivered via the Integration Service. Notifications are displayed as an in-app feed with an unread count badge on the dashboard.
Notification Types
- Shipment Shipped - Triggered when shipment status transitions to "shipped"
- Shipment Delivered - Triggered when FedEx confirms delivery
- Prescription Approved - Triggered after orchestrator approval pipeline
- Appointment Reminder - Sent 24 hours before a telehealth appointment
- New Message - Triggered when the care team sends a message
- Refill Upcoming - Sent 7 days before a scheduled refill
Returns persistent notifications for the authenticated patient, ordered by creation date descending.
Marks a notification as read.
API Endpoints
All /api/patient/* endpoints require a valid JWT session cookie.
The BFF enforces patient-scoped access - patients can only view their own data.
Authentication
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/patient/auth/send-code |
Send verification code via SendGrid |
| POST | /api/patient/auth/verify |
Verify code and set JWT session cookie |
| GET | /api/patient/auth/canvas-callback |
Canvas OAuth callback handler |
| POST | /api/patient/auth/logout |
Clear session cookie |
Patient Data
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/patient/profile |
Patient profile from Canvas FHIR |
| GET | /api/patient/prescriptions |
Prescription list from Canvas MedicationRequest |
| GET | /api/patient/shipments |
Shipment history from integration DB |
| GET | /api/patient/tracking/:trackingNumber |
FedEx tracking with ownership check |
| GET | /api/patient/messages |
Messages from Canvas FHIR Communications |
| POST | /api/patient/messages |
Send message to practitioner |
| GET | /api/patient/notifications |
Notification feed |
| POST | /api/patient/notifications/:id/read |
Mark notification as read |
| GET | /api/patient/refills |
Refill schedules list |
| POST | /api/patient/refills/:id/pause |
Pause refill schedule |
| POST | /api/patient/refills/:id/resume |
Resume paused schedule |
| POST | /api/patient/refills/:id/delay |
Delay next refill date |
| POST | /api/patient/refills/:id/cancel |
Cancel remaining refills |
| GET | /api/patient/availability |
Available appointment time slots |
| POST | /api/patient/appointments |
Book telehealth appointment |
| GET | /api/patient/appointments |
Appointment list with Zoom links |