Changelog
This page tracks meaningful changes to the partner-facing API surface, webhook contract, and integration guides. Contact partners@stealth.health for an email digest of changes.
We follow a "no breaking changes without a major version bump and a 90-day deprecation notice" policy. Additive changes (new fields, new events, new endpoints) ship in minor releases.
2026-09 — 0.2.7
Fixed
- A store order now keeps the
currencyyou sent it with. An order created as"currency": "cad"could be silently re-denominated tousd(or the reverse) when the customer opened the payment link, if the address on their Stealth patient profile sat in the other country — so a link quoted in CAD presented as USD on the checkout page, against a create response that saidcad. The currency you name is now held for the life of the order. On an order that also carried price overrides, the same re-denomination re-read each line from our catalog and discarded yourunit_price_cents; overridden lines are now preserved. No action needed, and no change to orders that were already in the right currency. POST /partner/store/ordersnested USshipping.addressand omit-shipping customer mode. A Stripe-style nestedshipping.addressobject (line1,city,state,postal_code,country: "US") was dropped as400 STORE_SHIPPING_INVALID/ADDRESS_REQUIREDbecauseaddresswas only read as a string. Nested objects and aliases (street,line1,shipping_address,ship_to) are now flattened.customermode may omitshippingagain — the hosted/transaction/{id}checkout collects the ship-to (Puramass embed).white_label_accountstill requires a complete ship-to before charge.
Added
- Per-order price and shipping overrides on
POST /partner/store/orders. An order may now name its own amounts instead of taking catalog pricing:items[].unit_price_centsreplaces a line's unit price,items[].shipping_rate_centsreplaces its shipping rate (still charged once per sub-store group), and a top-levelshipping_total_centsreplaces the computed shipping total outright — send0for free shipping, which is how you avoid the $35.00 default rate. Overrides are per request, so the same SKU can be priced differently on the next order, and they are honoured in bothcustomerandwhite_label_accountpayment modes.shipping_total_centswins over any per-lineshipping_rate_cents.unit_price_centsmay not fall below the wholesale price Stealth bills you for the item (STORE_PRICE_BELOW_WHOLESALE, 400, naming each SKU and its floor); shipping has no floor. Malformed amounts — negative, fractional, non-numeric, or above $100,000 — areSTORE_PRICE_OVERRIDE_INVALID(400). Additive: omit the fields and pricing is unchanged. See Referral Tier § 6.5b. authorize_onlyon Square.POST /partner/appointmentsacceptspayment.processor: "square"alongsidepayment.mode: "authorize_only"when Stealth routes your country's prescription payments to Square. The response returns Square application and location ids instead of a Stripeclient_secret; you mount the Square Web Payments card field and post the token to the newPOST /partner/appointments/{appointment_id}/square-authorization, which places the hold. Capture on approval and release on denial are unchanged. Additive: omitpayment.processor(or send"stripe") and nothing changes. See Clinical Tier § 5.4.- Authorize.net card payments, and Square for
embedded_checkout.POST /partner/appointmentsacceptspayment.processor: "authorize_net"withpayment.mode"embedded_checkout"or"authorize_only"when Stealth routes your country's prescription payments to Authorize.net. The response returns the Accept.jsapi_login_id+client_keyfor the routed merchant; tokenize the card with Accept.js and postopaque_datato the newPOST /partner/appointments/{appointment_id}/authorize-net-payment, which charges the card (embedded_checkout) or places a hold that is captured on approval and voided on denial (authorize_only).payment.processor: "square"now also works withembedded_checkout(an immediate charge through the existing/square-authorizationcall). An unrouted processor is refused with409 AUTHORIZE_NET_NOT_AVAILABLE/SQUARE_NOT_AVAILABLEbefore any appointment is created. Additive: omitpayment.processorand nothing changes. See Clinical Tier § 5.4. shipping_cents+total_centson thecustomer-mode create response. The hosted-link response now returns the shipping and order totals alongsidesubtotal_cents, so you can reconcile the quote before redirecting the customer. Additive — existing fields are unchanged.
2026-08 — 0.2.6
Added
white_label_accountpayment mode forPOST /partner/appointments. Clinical-tier partners can now submit an appointment withpayment.mode: "white_label_account"to have Stealth Health charge the white label's own card on file instead of collecting payment from the patient. At submit, the estimated clinic-use (wholesale) medication price plus a consult fee ($15 async / $40 sync) is charged; once the reviewing physician signs, the actual clinic-use total is trued up (delta charged or overage refunded); on clinical rejection, the medication portion is refunded and the consult fee is kept. Mirrors the existingwhite_label_accountmode already available onPOST /partner/store/orders. Emitstransaction.succeeded(transaction.type: "white_label_account_charge"at submit,"white_label_account_true_up"at signing) andtransaction.refunded(reason: "clinic_use_true_up") — see Clinical Tier § 13.2 Additional Clinical Events.GET /partner/store/orders/:transaction_idpolling endpoint andstore_order.payment_completewebhook. Partners integrating the hosted-checkout redirect from § 6.5bPOST /partner/store/orders(customerpayment mode) can now track a store order's payment status either by polling this new endpoint or by listening forstore_order.payment_complete, which POSTs to your registered webhook URL the moment the customer completes payment — carryingtransaction_id,order_id,payment_mode,currency, and the paid line items. See Referral Tier § 6.5c and the Glossary event catalog.customer+shippingblocks on store-order payloads. Thestore_order.payment_completewebhook event and theGET /partner/store/orders/:transaction_idpolling response now carry acustomerobject (first_name,last_name,email,phone) and ashippingobject (address,address2,city,state,zip,country) so partners can fulfil customer-paid orders without a manual address handoff.shippingis the address supplied at order creation when one was sent, otherwise the address the customer entered on the hosted checkout; it isnullwhen neither exists (typically while the order is stillpayment_pending). Additive — existing payload fields are unchanged.
2026-07 — 0.2.5
Added
authorize_onlypayment mode (pre-auth, capture on clinical approval).POST /partner/appointmentsnow acceptspayment.mode: "authorize_only"— Embedded Checkout with manual capture. Completing the embedded session places an authorization hold on the customer's card (funds reserved, not charged; appointmentpayment_statusbecomesauthorized). Stealth captures the hold when the reviewing physician approves (transaction.succeededwithcapture_of_authorization: true) and voids it when the patient is clinically ineligible (transaction.authorization_released, paired withreferral.denied/prescription.rejected) — an ineligible patient is never charged. Request shape matchesembedded_checkout(return_url,amount_cents,product_name). Per-partner opt-in (AUTHORIZE_ONLY_NOT_ENABLED, 403, without it); sandbox is open to every clinical-tier partner. Card-network authorization windows apply (typically 7 days) — see Clinical Tier § 5.4.transaction.authorization_releasedwebhook event — emitted when anauthorize_onlyhold is voided after a clinical denial. This is a release of held funds, not a refund. See Clinical Tier § 13.2.- Wearables Data API (clinical tier). Three new read-only, patient-scoped endpoints expose the wearables data patients generate by connecting devices (Oura, Fitbit, Garmin, WHOOP, Apple Health, Dexcom, and the rest of the Junction Sense catalog) in the (white-label) patient portal:
GET /partner/patients/:id/wearables/connections(device connection status per provider),GET /partner/patients/:id/wearables/summaries(per-day rollups — sleep, activity, body, workouts, heart rate, blood pressure, glucose; windows up to 90 days), andGET /partner/patients/:id/wearables/timeseries(intraday samples for one metric — CGM curves, HR traces; windows up to 31 days, paginated up to 5,000 samples/page). Production access requires the wearables BAA addendum (WEARABLES_ACCESS_REQUIRED, 403, without it — emailpartners@stealth.health); sandbox is open to every clinical-tier partner. Every read is PHI-audited. New error codesWEARABLES_ACCESS_REQUIRED,WEARABLE_DATE_INVALID,WEARABLE_METRIC_INVALID. See Clinical Tier § 12B. wearable.connection.*webhook events —wearable.connection.created,wearable.connection.revoked, andwearable.connection.errorfire when a patient you own connects, disconnects, or breaks auth with a wearable provider (requiresfeatures.wearables). Payloads carryprovider_slug+statusonly — no measurements; poll the summaries endpoint for data. No data-drop events are emitted (a CGM would generate hundreds per patient per day). See Clinical Tier § 12B.4.- Referral Tier § 6 cross-reference. The referral-tier API reference now points readers to the clinical guide for the PHI-bearing endpoint families (patients, lab orders & requisitions, documents, partner-submitted intake/prescriptions, payment modes), which are not available to referral-tier keys.
2026-06 — 0.2.4
Added
- Embedded Checkout payment mode for partner-submitted intake.
POST /partner/appointmentsnow acceptspayment.mode: "embedded_checkout", returning a Stripe Embedded Checkoutclient_secret(+session_id) instead of a hostedpayment_url. Partners render the payment form inside their own page and Stripe collects the full billing + shipping address natively. On completion, Stealth reconciles the collected address back onto the appointment (patientAddress,shippingAddress,patientCountry,patientJurisdiction) — HIPAA-audited asappointment.partner_checkout.address_applied— so the clinician sees a complete destination before review. New request fieldspayment.return_url(required https),payment.amount_cents,payment.product_name; new errorsRETURN_URL_REQUIRED,PAYMENT_AMOUNT_INVALID,CHECKOUT_AMOUNT_UNRESOLVED. See Clinical Tier § 5.4. - Embeddable messaging module (
@wearestealthhealth/messaging-embed). A drop-in React component and vanilla-JS UMD/CDN bundle that frames the secure patient messaging surface inside a partner app with a one-click SSO handoff. New endpointPOST /partner/patients/:id/sessionsmints a short-lived, single-use opaque embed token (the partner never receives a platform credential); the iframe exchanges it server-side for a session credential. Ships apostMessageevent API (messaging:ready,messaging:unread-count,messaging:tos-required,messaging:tos-accepted,messaging:error), in-iframe theming, ToS pre-display gating, and a mobile-fallback deep link. See Clinical Tier § 9.3. - Per-partner embed origin allowlist (
embed_allowed_origins). The patient app now emitsContent-Security-Policy: frame-ancestors 'self' <partner origins>on/embed/*, built from the union of every partner's configured origins. Manage your preprod + prod origins (bare hosts and*.partner.comwildcards accepted) via your account manager / the reporting portal's white-label Partner API step.
Changed
- Zero-downtime webhook-secret rotation. Rotating a partner's
webhook_secretnow opens a 7-day dual-verify window: deliveries are signed with both the new secret (X-Stealth-Signature) and the old secret (X-Stealth-Signature-Previous). Accept a delivery if either header validates against the secret you hold, deploy the new secret at your leisure, and the previous-signature header simply stops appearing after the window. See Clinical Tier § 17.2.
Fixed
embedded_checkoutresponse now returns thepublishable_keyneeded to mount Stripe. ThePOST /partner/appointmentsembedded_checkout201 previously returned onlyclient_secret+session_id, leaving partners with no way to initialize Stripe.js (Stealth is the merchant of record, so partners have no publishable key of their own) — mounting<EmbeddedCheckout>failed with a "publishable key is not configured" error. The response now includespayment.publishable_key(Stealth's account-scoped key, selected server-side by mode + currency/region) andpayment.stripe_account. Initialize withloadStripe(payment.publishable_key)— the key is account-scoped, so no separatestripeAccountoption is needed. Always read it from the response rather than hard-coding. See Clinical Tier § 5.4.- Idempotent replays of
embedded_checkoutsubmissions no longer fall back to the hosted response. Re-submitting aPOST /partner/appointmentsrequest with a previously usedpartner_referenceandpayment.mode: "embedded_checkout"previously returned the original appointment with a hostedpayment_urlblock. Replays now correctly returnpayment.mode: "embedded_checkout"and mint a fresh Checkout Session (Stripe client secrets are single-session), so expect a newclient_secret+session_idon each replay — don't cache the old one.
2026-05 — 0.2.3
Added
- Stealth-doctor sign path now fires
appointment.prescription_signed. Previously this event only emitted from the legacy JotForm pipeline and the partner-submitted-Rx flow. As of0.2.3, the doctor-portal queue handler (submitDoctorPrescription) and the standalone Rx flow (submitStandalonePrescription) also emit it whenever a partner-owned appointment is signed. Clinical-tier partners receive the documented slim shape (appointment_id,prescription.rx_id,prescription.medications[]); referral-tier partners continue to receivereferral.approvedonly. See Clinical Tier § 13.2.1 for the canonical payload. prescription.rejectedextended to the Stealth-doctor reject path. Until now this event was reserved for the partner-submitted-Rx async re-validation path. As of0.2.3, it also fires fromrejectAppointmentwhenever a Stealth physician declines to sign for a partner-owned appointment. The companionreferral.deniedevent still fires at the same instant — partners on the clinical tier should expect both. The newerror_codeenum (MEDICAL_CONTRAINDICATION,INCOMPLETE_INFORMATION,NOT_A_CANDIDATE) is documented in Clinical Tier § 13.2.2.
Notes for existing handlers
- The wire payloads match the schemas already documented in
0.2; this release is the implementation catching up to the spec. Partners who builtappointment.prescription_signed/prescription.rejectedhandlers per the docs do not need to change anything — they will start receiving events that previously did not fire. - Backfill is not provided. If you need a backfill for partner-owned appointments signed or rejected before
0.2.3, contactpartners@stealth.health— we can replay from the audit log on a per-partner basis.
2026-05 — 0.2.2
Added
- Clinical Tier — § 12 API Reference — Lab Orders & Requisitions. Four new clinical-tier endpoints expose the lab orders placed for partner-owned patients:
GET /partner/patients/:patient_id/lab-orders— list lab orders with status / collection-method filters.GET /partner/lab-orders/:lab_order_id— full order detail, including event timeline and any booked Patient Service Center appointment.GET /partner/lab-orders/:lab_order_id/requisition— signed-URL (default) or inline-PDF download of the lab requisition. 15-minute TTL on signed URLs; 90-day retention on the underlying PDF.GET /partner/lab-orders/:lab_order_id/results— structured biomarker results once the lab releases them (partialorfinal), withmissing_results[]for samples the lab couldn't process.
- Six new webhook events in the lab-order family:
lab_order.created,lab_order.requisition_ready,lab_order.sample_collected,lab_order.results_updated,lab_order.completed,lab_order.cancelled,lab_order.exception. Biomarker values are intentionally omitted from webhook payloads — partners re-fetch via the results endpoint. See Clinical Tier § 13.2 Additional Clinical Events and the Glossary event catalog. messageThread.updatedwebhook in the messaging family. Fires when a clinician or patient posts a message in a thread linked to a partner-owned appointment. Body is not included in the payload — partners pokeGET /partner/patients/:patient_id/messages?updated_after=...to pull the new content. See Clinical Tier § 9.1.- Clinical Tier — § 9.2 Partner-submit policy & patient-authored messages. New subsection documenting that messaging is read-only for partners and explaining the three supported patterns for patient-authored replies (SSO into the patient portal, iframe-embed with CSP allowlist, polling + read-only mirror).
- Lab Order Object and Lab Order Results Object added to § 14 Data Models (§ 14.8, § 14.9).
- New ID prefixes registered in the Glossary:
lab_(lab order),psc_apt_(Patient Service Center appointment). - Lab-orders-specific error codes added to § 15 Error Handling:
LAB_ORDER_NOT_FOUND,REQUISITION_NOT_READY,REQUISITION_EXPIRED,RESULTS_NOT_AVAILABLE,LAB_ORDER_ACCESS_REQUIRED.
Changed
- Lab orders / requisitions / biomarker results added as a new data class in the Clinical Tier § 3.3 "Where PHI Crosses the Wire" table.
- Section numbers in the Clinical Tier guide shifted by one to make room for § 12. Old § 12 Webhook Events → § 13; old § 13 Data Models → § 14; old § 14 Error Handling → § 15; old § 15 Rate Limits → § 16; old § 16 Best Practices → § 17 (with sub-sections 16.1–16.6 renumbered to 17.1–17.6). All in-document anchor refs and the cross-references in Glossary have been updated.
- Clinical Tier § 9.1 now documents the supported query parameters (
status,appointment_id,updated_after,limit,cursor) and thePATIENT_NOT_FOUND/CLINICAL_ACCESS_REQUIRED/QUERY_ERRORerror catalog. Endpoint behavior is unchanged.
Access requirements
- Production access to the lab-orders endpoints is gated on a per-partner BAA addendum acknowledging the additional PHI surface (panel selection + biomarker values). Sandbox is open to every clinical-tier partner. Email
partners@stealth.healthto enable in production. Requests without the addendum returnLAB_ORDER_ACCESS_REQUIRED.
2026-05 — 0.2.1
Added
- Questionnaire Embed Integration Guide added to the docs site. Covers the Vanilla JS drop-in script, the React/npm package (
@wearestealthhealth/questionnaire-embed), thepostMessageevent protocol, CSP allowlist, white-labelwl=param, version pinning, and the full questionnaire-ID → enroll-page → US-medication catalog. The embed is a delivery mechanism that pairs with any integration tier. - Referral Tier § 6.8
GET /partner/events— read-only endpoint to retrieve webhook events from the dead-letter queue (and any other delivery state) for replay and reconciliation. Filterable byevent_type,referral_id,partner_reference,delivery_status, and time range. 30-day retention. Backfills the previously-broken §6.8 reference in §7.5.
Fixed
- Broken intra-doc anchor in Prescriber-Partner § 6.1 that pointed to
#74-get-partnerproducts(Products endpoint actually lives at § 9.3).
2026-04 — 0.2
Added
- Clinical Tier — Partner-submitted prescriptions. Clinical-tier partners can now register their own licensed prescribers and submit prescriptions written by them via two new endpoints:
POST /partner/prescribers,GET /partner/prescribers,GET /partner/prescribers/{id},DELETE /partner/prescribers/{id}— manage the partner-prescriber registry.POST /partner/prescriptions— submit a prescription tied to a registered prescriber. Patient resolution accepts an existingpatient_id, an existingreferral_id, or an inline patient profile.- Two new webhook events:
prescription.receivedandprescription.rejected. - Controlled-substance handling: schedule II–V Rx requires a valid
dea_numberfor US prescribers; CDSA Schedule I–V Rx requires a Canadian provincial license. Partial details in Clinical Tier § 11.2.
- Clinical Tier — HIPAA & compliance section expanded with Stealth Health's technical controls (TLS 1.2+, AES-256 at rest, key handling, webhook integrity, sub-processor list) and a new § 17 Partner Implementation Best Practices (originally added as § 16; renumbered in 0.2.2).
- Partner documentation site (this site) launched at docs.stealth.health.
Changed
- All Google Cloud regional references updated to
us-east5(Columbus, Ohio).
Webhook events added
prescription.receivedprescription.rejected
2026-03 — 0.1 (Initial draft)
Added
- Referral Tier — initial spec covering enrollment URL minting, lifecycle webhooks, transactions, and shipment tracking. PHI-free.
- Clinical Tier — initial spec covering patient profiles, intake responses, appointments, prescriptions, transactions, and clinical-tier webhook events under a BAA.
- Prescriber-Partner Tier — initial spec for partners that own the patient intake and consume Stealth Health physician review and signing as a service.
- Authentication:
X-Partner-ID+X-Api-Key, HMAC-SHA256 signed webhooks (X-Stealth-Signature). - Standardized error code catalog and per-tier rate limits (300 req/min prod / 60 req/min sandbox).
How versioning works
- Major (
1.0,2.0) — breaking changes to request/response shapes, removed fields, or removed endpoints. Communicated 90 days in advance via email + this changelog. Old version stays available until the deprecation window closes. - Minor (
0.2,0.3) — additive changes. New fields are non-breaking; clients are expected to ignore unknown fields and unknown webhook event types. - Patch / docs — wording, example, or diagram updates with no behavioral change. Not separately versioned.
The Content-Type of all API responses includes the version, e.g. application/vnd.stealth.health.v1+json. The current version sent on the wire is documented in each tier guide.