HL Portal Documentation

Overview

HLC Portals is a multi-portal healthcare platform for Healthy Living Clinic, built with Next.js, Prisma, and MySQL.

The system includes:

  • Admin Portal
  • EMR Portal
  • CRM Portal
  • Patient Portal
  • Public Checkout + Consultation intake flow

It supports organization-scoped data, subscriptions, order lifecycle management, intake/questionnaires, prescriptions, workflow automations, and payment gateway switching (Stripe/NMI).

Recent updates included in this documentation:

  • Checkout template and cart workflow improvements
  • Processor-level payment visibility in EMR for Super Admin
  • Detailed checkout/payment failure causes returned by API/toasts
  • Subscription billing metadata persistence on checkout-created orders
  • Subscription switch/payoff implementation plan in docs/subscription-switching-plan.md
  • Security email templates, login alerts, welcome email, and email 2FA
  • Staff role access to both CRM and EMR portals

Technology Stack

  • Frontend: Next.js App Router, React, Ant Design
  • Backend/API: Next.js API Routes
  • Database: MySQL (via Prisma ORM)
  • Auth: JWT-based auth with server-side permission checks
  • Payments: Stripe and NMI
  • Messaging: SMTP email, Twilio SMS
  • Telephony: RingCentral integration (configurable)

Auth and Session Security

The current auth model is hardened beyond the original localStorage token flow.

  • Access tokens are validated server-side on protected API requests.
  • Refresh tokens are stored in HttpOnly cookies and in the database.
  • Refresh tokens are rotated on refresh.
  • Logout clears auth cookies and deletes refresh sessions.
  • Reset-password and admin session-revoke actions invalidate refresh sessions.
  • Client-side persisted auth state keeps only basic user profile info, not raw JWTs.

Important behavior:

  • Access tokens are still short-lived JWTs, so a currently issued access token can remain valid until expiry.
  • Refresh-session revocation is immediate.

Permission Model

Permissions now work in three layers:

1. Organization-level feature toggles

Stored in:

  • OrganizationSettings.portalPermissions

Configured by:

  • Super Admin in Admin -> Organizations

Purpose:

  • defines the feature ceiling for an organization
  • hides blocked portal sections in the UI
  • blocks the corresponding backend routes

2. Organization custom roles

Stored in:

  • OrganizationRole.portalPermissions

Configured by:

  • Admin -> Users
  • available to SUPER_ADMIN and ORG_ADMIN

Purpose:

  • allows each organization to create extra roles such as Billing, Care Coordinator, or EMR Read Only
  • narrows access within the organization-level feature ceiling
  • can be assigned to users without changing the built-in system role enum

Built-in system roles still exist:

  • SUPER_ADMIN
  • ORG_ADMIN
  • CLINICIAN
  • STAFF
  • PATIENT

Custom roles are layered on top of those built-in roles. They do not replace SUPER_ADMIN behavior.

3. User-level feature overrides

Stored in:

  • User.portalPermissionOverrides

Status:

  • backend field is still present for future use
  • per-user override UI is intentionally hidden right now
  • current admin flow should use org-level toggles plus custom roles instead

Effective permission rule

  • SUPER_ADMIN bypasses all permission toggles
  • everyone else gets:
    • organization permission
    • then custom-role permission, if assigned
    • then user override, if that layer is re-enabled later
  • lower layers can only narrow access
  • if the organization disables a feature, the user cannot re-enable it below that ceiling

Current permission keys

  • admin.users.manage
  • admin.audit_logs.view
  • admin.settings.view
  • admin.settings.branding.edit
  • admin.settings.domains.manage
  • admin.settings.communication.edit
  • admin.settings.payment_gateway.edit
  • admin.settings.security.edit
  • crm.carts.view
  • crm.checkout_template.manage
  • crm.templates.manage
  • crm.workflows.manage
  • emr.dashboard.view
  • emr.patients.view
  • emr.appointments.view
  • emr.consultations.view
  • emr.subscriptions.view
  • emr.orders.view
  • emr.processor_logs.view
  • emr.drugs.view
  • emr.questionnaires.view


Portal Modules

Admin Portal

  • Organization management
  • User management
  • Global/organization settings
  • Payment gateway configuration
  • Audit logs

EMR Portal

  • Patients, charts, consultations, appointments
  • Orders and subscriptions
  • Intake review workflow (approve / needs correction / reject)
  • Prescriptions and fulfillment flow
  • Drugs catalog and product-to-drug mapping
  • Processor Logs (Super Admin): cross-org processor event timeline including checkout failures

CRM Portal

  • Leads, campaigns, templates, tasks
  • Product catalog (store products)
  • Workflow engine with triggers, conditions, steps, and run logs
  • Checkout template builder/editor
  • Security and welcome email template management

Payment Recovery

The platform now supports a first-pass subscription payment recovery flow on top of the existing payment.failed workflow trigger.

Current recovery model

  • recurring subscription renewal charges that fail are marked with recovery metadata on the related order
  • recovery retries are processed by the existing subscription processor cron
  • patient portal shows a payment-recovery warning and sends the patient to billing
  • EMR subscriptions page shows a payment recovery queue

Cron requirement

Yes, payment recovery needs the subscription processor cron to keep running.

That processor handles both:

  • recurring subscription billing
  • payment recovery retries

Use this workflow trigger

  • payment.failed

For a renewal/dunning workflow, filter it to subscription failures using:

  • subscription.id
  • order.subscriptionId
  • order.sourceContext

Recovery fields available in workflow payload

On the order object:

  • order.recoveryStatus
  • order.retryCount
  • order.maxRetryCount
  • order.nextRetryAt
  • order.lastRetryAt
  • order.recoveryUrl

At the top level:

  • paymentRecovery.status
  • paymentRecovery.retryCount
  • paymentRecovery.maxRetryCount
  • paymentRecovery.nextRetryAt
  • paymentRecovery.lastRetryAt
  • paymentRecovery.recoveryUrl
  • paymentRecovery.autoChargeStatus
  • paymentRecovery.autoChargeMessage

Recovery status meanings

  • RETRY_SCHEDULED
    • the system will retry the original renewal charge on the saved payment method
  • NEEDS_PAYMENT_METHOD
    • no usable payment method is available; patient action is required
  • RESOLVED
    • the renewal payment recovered successfully
  • FAILED_FINAL
    • retry attempts were exhausted

Recovery settings behavior

Configured in:

  • Admin -> Settings -> Payment Recovery

Fields:

  • Enable payment recovery notifications
    • only controls automatic email/SMS sends
    • it does not stop retry attempts
  • Max retry count
    • total number of recovery retry attempts before the order moves to FAILED_FINAL
  • Retry delays in days
    • comma-separated schedule, for example 1,3,5
    • retry 0 uses the first value
    • retry 1 uses the second value
    • retry 2 uses the third value
  • Send retry reminder on or after retry count
    • controls when the configured retry reminder email/SMS is sent
    • example:
      • 1 = send after the first failed retry
      • 2 = send after the second failed retry
    • if set to 0, it still does not send before retries start
    • 0 behaves like “send on the first failed retry” because reminders are only evaluated after a retry attempt happens

Does disabling notifications stop retries?

No.

  • retries continue according to the configured retry count and delay schedule
  • disabling notifications only stops the automatic email/SMS sends

Missing or expiring cards

Patient portal behavior:

  • if a patient has an active subscription and no saved payment method, a warning banner is shown on the dashboard
  • if a saved card is expired or within 30 days of expiry, a dashboard banner is shown

Expiry reminders:

  • orgs can enable card-expiry reminder email/SMS in Admin -> Settings -> Payment Recovery
  • reminder timing is configured with Send on these days before expiry
  • use a comma-separated schedule such as 30,7,3,1
  • the same configured email template is reused for each matching reminder day
  • reminder processing runs from the same subscription processor cron

Example workflow branches

Ask the patient to update card details

  • Trigger: payment.failed
  • Condition: paymentRecovery.status equals NEEDS_PAYMENT_METHOD
  • Action: send email or SMS with billing link
  • Action: webhook to external support/CRM if needed

Second or third retry follow-up

  • Trigger: payment.failed
  • Condition: paymentRecovery.retryCount greater_than 1
  • Action: send a stronger reminder email

Final failure escalation

  • Trigger: payment.failed
  • Condition: paymentRecovery.status equals FAILED_FINAL
  • Action: pause or cancel subscription
  • Action: notify staff

Patient recovery URL

Current recovery URL:

  • /patient/invoices?recovery=1

That page highlights failed renewal recovery, saved cards, and the add-payment-method flow.

Current behavior:

  • paymentRecovery.recoveryUrl resolves to /patient/invoices?recovery=1
  • it is currently a fixed system URL, not a custom per-organization URL

Workflow Builder Versioning

The workflow system now supports a safe V1/V2 coexistence path.

Current model

  • Workflow.builderVersion
    • V1
    • V2
  • Workflow.blueprint
    • legacy executable workflow definition
  • Workflow.blueprintV2
    • new builder definition storage for V2 workflows

Why this exists

This allows the team to:

  • keep existing production workflows on the current V1 builder
  • create and edit V2 workflows in parallel
  • test V2 structure and UI without replacing the active legacy workflow first
  • remove or cut over V1 only after V2 is validated

Current execution behavior

  • V1 workflows:
    • can be edited in the legacy workflow builder
    • can be activated
    • are executed by the current workflow engine
  • V2 workflows:
    • open in the dedicated V2 builder route
    • store their definition in blueprintV2
    • are intentionally not executed yet by the live workflow engine
    • test execution is intentionally blocked for now

Current UI routing

  • legacy builder:
    • /crm/workflows/[id]
  • new builder:
    • /crm/workflows-v2/[id]

Recommended migration pattern

  1. Leave the live V1 workflow active.
  2. Create a new V2 workflow for the same business process.
  3. Build and validate the V2 definition/UI.
  4. When the V2 executor is ready and tested, cut over deliberately.
  5. Only then deactivate/remove the old V1 workflow.

Patient Portal

  • Dashboard, profile, appointments, records
  • Invoices and payments
  • Saved payment methods
  • Subscriptions
  • Intake form completion/resubmission

Core Business Flow

  1. Patient checks out from cart/public buy link.
  2. Payment succeeds.
  3. Order is created (and subscription if applicable).
  4. Patient is redirected to consultation intake forms.
  5. Order moves through questionnaire/review statuses:
    • AWAITING_REQUIREMENTS
    • PROCESSING (submitted, pending medical review)
    • AWAITING_SCRIPT (approved and ready for Rx)
    • REJECTED (if medically rejected)
  6. If rejected, linked subscription(s) can be auto-cancelled with reason logged.
  7. Prescription is sent; fulfillment/shipping statuses continue in EMR.

Payment and Processor Event Behavior

Active Gateway Processing

  • Card token/profile can be stored for both Stripe and NMI in checkout flow.
  • Charge always runs through the currently active organization gateway setting.
  • If active gateway tokenization fails, checkout returns explicit gateway failure details.

Checkout Failure Handling

  • On failed checkout payment:
    • cart is marked FAILED
    • cart.failed and payment.failed workflow triggers fire
    • API response returns detailed gateway decline cause when available
    • temporary failed order/invoice rows created during checkout are cleaned up to avoid EMR order clutter
  • Because failed order rows are cleaned up, processor failures are still visible via EMR Processor Logs.

Detailed Failure Cause in API/Toast

  • Failure messages now include gateway reason text and codes when available.
  • Example:
    • Payment failed: Card Type Verification Error (code: 461/CV). You can retry from this same cart link.

Checkout Flow (With URL Placeholders)

Use this pattern for hosted checkout domains/environments.

1) Entry URL (Buy Link)

  • Single or multiple products:
    • https://<checkout-domain>/buy/<productId>
    • https://<checkout-domain>/buy/<productId1>,<productId2>,<productId3>

Example:

  • https://checkout.healthyliving.clinic/buy/PRODUCT_A,PRODUCT_B

Behavior:

  • System resolves product IDs.
  • Creates/loads a cart session.
  • Redirects to cart page.

2) Cart URL

  • https://<checkout-domain>/cart/<cartId>

Example:

  • https://checkout.healthyliving.clinic/cart/<UNIQUE_CART_ID>

Behavior:

  • Shows cart items and pricing.
  • Collects patient details (name/email/phone/password) and payment.
  • Creates patient account if new.
  • Persists unique cartId for abandoned cart / retry workflows.

3) Checkout Submit

  • API route (server-side):
    • POST /api/public/cart/<cartId>/checkout

Behavior on success:

  • Payment captured (based on active gateway).
  • Order created.
  • Subscription created (if product type is subscription).
  • Order metadata links subscription IDs when applicable.
  • For subscription checkout orders, billing metadata is saved:
    • billingInterval
    • billingIntervalCount
    • cycleNumber (starts at 1)
    • billingPeriodStart
    • billingPeriodEnd
  • Cart marked completed.

4) Post-Payment Redirect to Consultation

  • https://<consultation-domain>/consultation/start/<sessionKey>

Example:

  • https://consultation.healthyliving.clinic/consultation/start/<SESSION_KEY>

Local testing example:

  • http://localhost:3200/consultation/start/<SESSION_KEY>

Behavior:

  • Loads questionnaires mapped to purchased product(s).
  • Patient submits intake forms.
  • Status flow begins (AWAITING_REQUIREMENTS -> PROCESSING -> etc.).

5) Completion Redirect to Patient Portal

  • https://<patient-portal-domain>/patient/dashboard

Local example:

  • http://localhost:3200/patient/dashboard

Behavior:

  • Patient lands in portal after consultation/intake completion.
  • Can track invoices, subscriptions, intake status, and next actions.

6) Suggested URL Placeholder Set

  • Checkout domain: https://<checkout-domain>
  • Consultation domain: https://<consultation-domain>
  • Patient portal domain: https://<patient-portal-domain>
  • Buy URL: https://<checkout-domain>/buy/<productIdsCsv>
  • Cart URL: https://<checkout-domain>/cart/<cartId>
  • Consultation URL: https://<consultation-domain>/consultation/start/<sessionKey>
  • Portal URL: https://<patient-portal-domain>/patient/dashboard

7) Key Identifiers in Flow

  • productId: product identifier from CRM catalog
  • cartId: unique cart identifier (for abandoned/retry workflows)
  • sessionKey: consultation session key used to fetch assigned questionnaires
  • orderId: created order ID in EMR/CRM data
  • subscriptionId / subscriptionIds: linked subscription records for recurring products

Embeddable Cart Drawer

Use this when you want to place a lightweight "Add to Cart" or "Start Checkout" button on an external website and have HLC create the cart in the backend.

Backend Endpoint

  • Public cart-create API:
    • POST /api/public/cart/create

Request body:

{
  "productIds": ["96d4d3eb-88f1-4d7b-9d85-73b34b3745f1"],
  "checkoutTemplateId": "default"
}

Accepted fields:

  • productIds: array of product IDs or CSV string
  • checkoutTemplateId: optional checkout template ID
  • temp: optional alias for template ID
  • sourcePath: optional tracking path
  • sourceDomain: optional tracking domain

Response includes:

  • cartId
  • checkoutUrl
  • items
  • subtotal
  • discount
  • tax
  • total
  • paymentGateway

Embed Script

Static script location:

  • /embed/hlc-cart-drawer.js

Source/edit file:

  • scripts/hlc-cart-drawer.source.js

Build command after edits:

npm run embed:build

This script:

  • calls the public cart-create endpoint
  • can render a cart icon into a mount element
  • can open a right-side drawer immediately or only when cart icon is clicked
  • renders cart items, quantity, pricing, and remove actions
  • links the user into the real HLC cart/checkout page

Localhost Test

Use this first while local app is running on http://localhost:3200:

<div id="portals-cart-app"></div>

<script>
  window.HLCCartDrawerConfig = {
    apiBase: "http://localhost:3200",
    cartMountSelector: "#portals-cart-app",
    openOnAdd: true,
    title: "Your Cart",
    checkoutLabel: "Continue to Checkout",
    accentColor: "#1a3c5e"
  };
</script>
<script src="http://localhost:3200/embed/hlc-cart-drawer.js"></script>

<button data-hlc-product-ids="96d4d3eb-88f1-4d7b-9d85-73b34b3745f1">
  Add to Cart
</button>

Production / Dev Host Usage

Replace apiBase with your actual HLC environment:

<div id="portals-cart-app"></div>

<script>
  window.HLCCartDrawerConfig = {
    apiBase: "https://dev.healthyliving.clinic",
    cartMountSelector: "#portals-cart-app",
    openOnAdd: true,
    title: "Your Cart",
    checkoutLabel: "Continue to Checkout",
    accentColor: "#1a3c5e"
  };
</script>
<script src="https://dev.healthyliving.clinic/embed/hlc-cart-drawer.js"></script>

<button data-hlc-product-ids="96d4d3eb-88f1-4d7b-9d85-73b34b3745f1">
  Add to Cart
</button>

Add Silently, Open From Cart Icon

Use this when clicking Add to Cart should only update the cart badge and not open the drawer immediately.

<div id="portals-cart-app"></div>

<script>
  window.HLCCartDrawerConfig = {
    apiBase: "https://dev.healthyliving.clinic",
    cartMountSelector: "#portals-cart-app",
    openOnAdd: false,
    title: "Your Cart",
    checkoutLabel: "Continue to Checkout",
    accentColor: "#1a3c5e"
  };
</script>
<script src="https://dev.healthyliving.clinic/embed/hlc-cart-drawer.js"></script>

<button
  data-hlc-product-ids="96d4d3eb-88f1-4d7b-9d85-73b34b3745f1"
  data-hlc-open-on-add="false">
  Add to Cart
</button>

Behavior:

  • item is added to current cart
  • badge count increases on cart icon
  • clicking cart icon opens drawer
  • clicking the same product button again does not increase quantity; it opens the existing drawer/cart instead

Multiple Products

<button
  data-hlc-product-ids="prod-id-1,prod-id-2"
  data-hlc-template-id="tpl-test">
  Start Checkout
</button>

Direct JavaScript API

<script>
  HLCCartDrawer.open({
    apiBase: "http://localhost:3200",
    productIds: ["96d4d3eb-88f1-4d7b-9d85-73b34b3745f1"],
    checkoutTemplateId: "default"
  });
</script>

Add without opening:

<script>
  HLCCartDrawer.add({
    apiBase: "https://dev.healthyliving.clinic",
    productIds: ["96d4d3eb-88f1-4d7b-9d85-73b34b3745f1"],
    openOnAdd: false
  });
</script>

Data Attributes Supported

  • data-hlc-product-ids
  • data-hlc-template-id
  • data-hlc-title
  • data-hlc-checkout-label
  • data-hlc-accent
  • data-hlc-open-on-add

Config Options

  • apiBase
  • cartMountSelector
  • openOnAdd
  • buttonSelector
  • title
  • checkoutLabel
  • accentColor

Current Scope

  • Drawer shows created cart contents and quantity returned from backend
  • Existing embed cart is reused and new items are merged into the same cart
  • Clicking an already-added product button again does not increment quantity; it reopens the current cart drawer
  • Cart badge count is stored in browser local storage and refreshed from HLC cart API
  • Product image URLs are normalized against HLC apiBase
  • Removing an item from the drawer updates the backend cart and real checkout page
  • Removing the last item deletes the cart from backend, clears stored cart state, and prevents abandoned-cart follow-up on an empty cart
  • Checkout button always links to the real HLC cart page
  • CORS is enabled on /api/public/cart/create for external-site usage
  • Quantity increment/decrement inside the drawer is not implemented yet

Order Statuses

Configured order statuses include:

  • AWAITING_FULFILLMENT
  • AWAITING_PAYMENT
  • AWAITING_REQUIREMENTS
  • AWAITING_REQUISITION
  • AWAITING_RESULTS
  • AWAITING_SCRIPT
  • AWAITING_SHIPMENT
  • INCOMPLETE
  • PENDING
  • PROCESSING
  • REJECTED
  • RETURNED
  • SHIPPED
  • COMPLETED
  • CANCELLED
  • REFUNDED

Roles and Portal Access

Roles in active use

  • SUPER_ADMIN
  • ORG_ADMIN
  • CLINICIAN
  • STAFF
  • PATIENT

Portal access by role

  • SUPER_ADMIN: Admin + CRM + EMR + cross-org context
  • ORG_ADMIN: Admin + CRM + EMR (org-scoped)
  • CLINICIAN: EMR
  • STAFF: CRM + EMR
  • PATIENT: Patient Portal only

Staff role behavior

  • Staff can log in normally.
  • Staff can access:
    • /crm/*
    • /emr/*
  • Staff does not get Admin Portal access.
  • CRM/EMR sidebar portal switching shows:
    • CRM
    • EMR

Login landing behavior

Login redirect still uses the user record's portal field as the default landing portal:

  • ADMIN -> /admin/dashboard
  • CRM -> /crm/dashboard
  • EMR -> /emr/dashboard
  • PATIENT -> /patient/dashboard

This means a STAFF user can have default landing on CRM while still being allowed to switch to EMR.

EMR Processor Logs (Super Admin)

Location:

  • EMR left navigation: Processor Logs

Purpose:

  • Unified processor event view across checkout and portal-originated payment events.
  • Includes events even when checkout-failed temporary orders are cleaned up.

What is shown:

  • Event type (TOKENIZATION_FAILED, CUSTOMER_PROFILE_LINKED, PAYMENT_SUCCEEDED, PAYMENT_FAILED, PAYMENT_REFUNDED)
  • Gateway (STRIPE / NMI)
  • Organization, patient, order/cart/invoice references
  • Transaction/charge/customer(vault) identifiers
  • Processor response codes and gateway message text

Scope behavior:

  • Page is available to SUPER_ADMIN only.
  • It follows global org context selector from EMR header (org_context) for filtering.

Cart Statuses

  • OPEN: Cart created and still in progress.
  • FAILED: Checkout payment attempt failed (cart can be retried from same link).
  • COMPLETED: Payment succeeded and checkout finalized.
  • ABANDONED: Cart was idle past configured timeout and marked by cart processor job.

Repository Structure (High Level)

  • app/: Next.js pages and API routes
    • app/admin/*
    • app/emr/*
    • app/crm/*
    • app/patient/*
    • app/api/* (all server routes)
  • lib/: Shared server/client utilities
    • Auth, payments, workflows, subscription billing, audit, encryption
  • prisma/: Prisma schema and seed script
  • scripts/: Data import and PHI rotation scripts
  • store/: Zustand auth store

Security and Authentication

Current security features

  • JWT access + refresh auth
  • Login audit logs
  • Login alert emails
  • Suspicious/new-device login alert emails
  • Failed login tracking
  • Account lockout after configurable failed attempts
  • Email-based 2FA
  • Welcome email on new user creation
  • Forgot password + reset password flow

Global security controls

Configured in Admin Settings:

  • Login alert on every login
  • Suspicious login alert
  • Max failed login attempts
  • Lockout duration (minutes)
  • Email 2FA enable/disable
  • Admin-only 2FA scope

Per-user security controls

Configured in Admin -> Users:

  • Enable/disable per-user login alerts
  • Force 2FA
  • Exempt user from org-wide 2FA

2FA behavior

  • If login requires 2FA:
    1. password is verified
    2. 6-digit code is emailed
    3. user submits code on login page
    4. session is finalized and dashboard redirect happens

Meaning of settings:

  • Email 2FA: enables org-wide email 2FA
  • Admin-only 2FA: when enabled, org-wide 2FA applies only to non-patient/admin-like users

If Email 2FA is off, Admin-only 2FA alone does nothing.

Security email templates

Built-in protected templates include:

  • Security: Login Alert
  • Security: Suspicious Login Alert
  • Security: Forgot Password
  • Security: Password Reset Success
  • Security: Failed Login Warning
  • Security: Account Locked
  • Security: 2FA Code
  • Welcome: New User

Behavior:

  • protected templates are not deletable
  • default content can be restored
  • test email can be sent from template editor
  • security templates use HTML mode to preserve email-safe HTML markup

Merge tags commonly available in security emails

  • {{organization.name}}
  • {{organization.slug}}
  • {{organization.email}}
  • {USER.FIRSTNAME}
  • {USER.LASTNAME}
  • {USER.EMAIL}
  • {USER.PORTAL}
  • {{portalLabel}}

Event-specific examples:

  • {{otpCode}}
  • {{resetUrl}}
  • {{expiryMinutes}}
  • {{remainingAttempts}}
  • {{lockedUntil}}
  • {{ipAddress}}
  • {USERAGENT}
  • {{loginUrl}}

SMTP behavior

  • security/welcome/template test emails use organization SMTP settings from DB first
  • env SMTP values act as fallback
  • template-level From Name / From Email overrides are supported
  • if template email is blank, org SMTP sender is used

Welcome email behavior

  • Welcome email is sent when a user is newly created:
    • from Admin -> Users
    • from checkout/patient account creation
  • guarded by audit log so it sends once per user creation event

Webhook Response Data In Later Steps

Webhook steps (http_post) do not only send data. Their response is available to later workflow steps.

After a webhook step runs, later conditions/actions can use:

  • httpPost.ok
  • httpPost.status
  • httpPost.request
  • httpPost.response

Examples:

  • condition on webhook success:

    • field: httpPost.ok
    • operator: equals
    • value: true
  • condition on decision returned by webhook:

    • field: httpPost.response.decision
    • operator: equals
    • value: approve
  • condition on eligibility returned by webhook:

    • field: httpPost.response.eligible
    • operator: equals
    • value: true

If multiple webhook steps exist in the same workflow:

  • httpPost.* refers to the latest webhook step output
  • use stepResults.<stepId> when you need a specific earlier webhook result

Example Intake Flow

Example workflow:

  1. Trigger: intake.completed
  2. Action: http_post
    • send full payload with {{payload}}
  3. Condition:
    • httpPost.response.decision equals approve
  4. If true:
    • continue next step
  5. If false:
    • update_order_status
    • update_subscription_status
    • send email/SMS/webhook

Organization Scope

  • Core data is organization-scoped.
  • Super Admin can access cross-organization context.
  • Org Admin/clinical roles should operate within assigned organization scope.

HeyMarket SMS Integration

HeyMarket is supported as an alternative SMS provider alongside Twilio.

Configuration

Admin Settings → Communication → SMS Provider:

  • Toggle: Twilio | HeyMarket
  • HeyMarket fields: API ID, API Secret, Inbox ID, Creator ID

How it works

  • Setting smsProvider = "heymarket" in OrganizationSettings routes all outbound SMS through HeyMarket
  • JWT auth: apiId||apiSecret as HS256 signing key, payload {iss: apiId, iat: now}
  • JWT is generated server-side using Node.js crypto.createHmac (no external library)
  • Endpoint: POST https://api.heymarket.com/v1/message/send

Fallback order

  1. HeyMarket (if smsProvider = "heymarket" and credentials configured)
  2. Twilio DB credentials (if configured in org settings)
  3. Twilio env vars (TWILIO_*) as final fallback

Files

  • lib/workflow-engine.tssendSms(), buildHeyMarketJwt(), sendSmsViaHeyMarket()
  • lib/payment-recovery-notify.ts — HeyMarket-aware SMS on payment recovery
  • lib/payment-method-expiry-notify.ts — HeyMarket-aware SMS on card expiry
  • app/admin/settings/page.tsx — HeyMarket settings UI tab

Order Tracking

When an order status is SHIPPED, AWAITING_SHIPMENT, COMPLETED, or RETURNED, a Shipping & Tracking section is shown in the order detail modal.

Fields

FieldDescription
carrierCarrier name (e.g. UPS, FedEx, USPS)
trackingNumberCarrier tracking number
trackingUrlFull tracking URL
shippedAtTimestamp — auto-set when status transitions to SHIPPED

Behavior

  • Fields are editable inline in the EMR order modal
  • "Save Tracking" saves without changing order status
  • shippedAt is auto-populated when status is first set to SHIPPED

License

Proprietary software for Healthy Living Clinic. Unauthorized use, distribution, or modification is prohibited unless explicitly approved.