What changed in v2
Webhooks v2 introduces two independent improvements:
| Feature | v1 (legacy) | v2 |
|---|---|---|
| Payload versioning | Full raw event objects, including PHI | Sanitized envelopes with per-category allowlists |
| Signature header | sha256={base64_hmac} over body only | t={unix_ts},sha256={base64_hmac} over t.body — replay protection included |
api_version field | Not present | "2026-08-17" |
Existing webhooks default to v1. Newly created webhooks default to v2.
Payload versioning
Every v2 payload carries a fixed api_version field:
{
"event_type": "order_created",
"event_id": "event::abc123",
"api_version": "2026-08-17",
"timestamp": "2026-05-30T14:00:00.000Z",
"timestamp_epoch": 1780149600,
"affiliate_id": "aff::xyz",
"order_id": "order::abc"
}The api_version value is 2026-08-17. It identifies the payload schema and will be bumped if a breaking change is ever made.
timestampis the delivery-attempt time, not the event's creation time — it is regenerated on every retry.timestamp_epochis the same instant as integer Unix seconds. Don't use either to order or dedupe events; useevent_idfor dedup and reconcile state per entity for ordering (see Delivery & Retries).
PHI sanitization — per-category allowlists
PHI is removed from v2 payloads at dispatch time. Each event category has an explicit allowlist governing which eventData fields pass through; any field not on the list is stripped before delivery. patient_id does not appear on any category's allowlist below — but every v2 payload also carries a patient-identity block added by a separate enrichment step that isn't governed by the category allowlist. patient_marketing_consent and is_returning_patient are always present (default false). patient_id and patient_external_id are omitted when unavailable — never sent as null. patient_id is present when the event has a linked patient; patient_external_id is present only when that patient also has an external identifier.
is_returning_patient is per-patient, not per-order: true when the patient already has another ecommerce order in paid, processing, or completed.
| Category | Allowed fields |
|---|---|
| ORDER | order_id, order_status, case_status, case_id, ecommerce_order_id, journey_id, commerce_status, product_variation_ids, subscription_status, total_value_cents, currency, order_tag, patient_conversation_id, channel_type, visit_type, visit_id, external_case_identifier, external_case_status, order_asset_id, unlocked_at, order_external_identifier, asset_link, titration_config_id, titration_direction, config_name, status_transition, questionnaire_id, location_id, previous_product_variation_id, current_product_variation_ids, future_product_variation_ids, current_pv_count, future_pv_count, future_prescriptions_to_create, video_requested, reason, message_type, order_link, reminder_type, attribution, first_purchase, plan_type |
| PRESCRIPTION | prescription_id, prescription_status, pharmacy_id, order_id, commerce_status, product_variation_ids, visit_id, video_requested, reason, journey_id, attribution, instructions |
| SHIPPING | order_id, prescription_id, shipment_status, commerce_status, tracking_number, tracking_url, shipping_carrier, journey_id, attribution |
| PHARMACY | prescription_id, pharmacy_id, pharmacy_status, order_id, commerce_status, reason, journey_id, attribution |
| PAYMENT | order_id, total_value_cents, currency, payment_status, consultation_id, journey_id, attribution |
| LAB | lab_order_id, lab_order_status, order_id, commerce_status, interpretation, lab_name, external_order_id, provider_order_status, reported_at, provider_event_type, latest_provider_event_status, journey_id, attribution |
| SUPPORT | support_case_id, support_case_status, support_case_name, support_case_category, order_id, external_case_identifier, external_case_status, external_case_url |
| JOURNEY | journey_id, template_id, template_display_name, journey_type, ecommerce_order_id, started_at, completed_at, abandoned_at, total_value_cents, currency, products, coupon_code, recovery_url, recovery_url_expires_at, recovery_status, recovery_missing_details, had_cart, commerce_status, product_variation_ids, subscription_status, attribution, first_purchase, plan_type, intake_form_id, intake_form_instance_id, intake_form_instance_ids |
| COMMERCE | ecommerce_order_id, subscription_id, subscription_status, commerce_status, total_value_cents, currency, payment_status, product_variation_ids, journey_id, order_id, refund_amount_cents, refund_type, attribution, first_purchase, plan_type, cycle_number |
| PATIENT | (empty — no eventData fields exposed beyond the universal patient-identity fields above) |
| INTERNAL | (empty — no eventData fields exposed beyond the universal patient-identity fields above) |
There is no SUBSCRIPTION or CHANNEL category — subscription fields (subscription_id, subscription_status) are exposed under COMMERCE and ORDER/JOURNEY instead.
product_display_names and products[].display_name are not delivered. Product line items are { product_variation_id, quantity, unit_price_cents, is_addon }. Money is integer cents (84900 = $849.00).
first_purchase is true only when the order is not a renewal. subscription_renewed always sends first_purchase: false. journey_contact_captured never includes it (no order yet). journey_abandoned includes it only when had_cart is true. See Subscription Renewed.
attribution (including optional landing_url / referrer_url on first/last touch) is documented in Attribution Passthrough. SUPPORT, PATIENT, and INTERNAL do not receive it.
Beyond the allowlist, a separate blocklist strips known-PHI keys (email, phone, firstName/lastName, dateOfBirth, address/city/state/zip, medications, allergies, diagnosis, clinicalNotes, credentials/tokens, and others) as defense in depth if they ever appear on an allowlisted path.
Field coverage on v2 payloads
The allowlist above only keeps an eventData field if its name matches an allowlist entry (directly, or via a small alias table). Events introduced alongside the v2 payload system (Journey, Commerce/subscription, Payment events) use field names that match their category's allowlist, so their v2 payloads carry full detail. Patient events (patient_enrolled) carry full detail too, but for a different reason — their entire meaningful payload is the universal patient-identity fields described above, not anything from the (empty) PATIENT category allowlist.
Most events now include their event-specific fields directly in v2 payloads. Where detail is not included in the payload itself, it is retrievable through the authenticated API using the entity IDs the payload delivers — v2 payloads are designed as notifications, not data transfers. Status reasons and order-history details are retrievable through dedicated endpoints without requiring v1 payloads.
Migration path
- Create a new webhook or use the upgrade endpoint to switch an existing webhook to v2 payload delivery.
- Update your consumer to verify the new signature format (see Signature Verification).
- Remove any code that expects PHI fields — they will not appear in v2 payloads.
- If you need to roll back from v2 to v1, use the rollback endpoint (requires master token).
Event dictionary
The webhook dictionary defines 60 event types. 58 are public (returned by getDictionary). Two are internal-only (never delivered to affiliate webhooks). One public event (pharmacy_rejected_prescription) is defined but not yet implemented and is excluded from Affiliate Admin subscription — 57 events are command-subscribable, including journey_contact_captured and custom_intake_form_completed. All 57 command-subscribable events are emitted by live code paths. To retrieve the full list programmatically:
GET /webhooks/actions/getDictionaryNote this endpoint lives on the legacy v1 webhook management API (/webhooks/...), not the /v2/webhooks/... module — it predates payload versioning and works the same regardless of which payload version your webhook is configured for. See the getDictionary endpoint for the response shape.
For a complete per-event breakdown of which fields are guaranteed, conditional, or not delivered, see Field Guarantees.
For narrative documentation of individual events (purpose, example payload), see the event pages under each category: Order, Prescription, Pharmacy, Lab, Payment, Journey, Commerce, Patient, and Support Events.
Versioning guarantees
api_version: "2026-08-17"is the current v2 schema version (journey and generic envelopes share this value).- Within
2026-08-17, fields are additive except the documented removal ofproducts[].display_name/product_display_names. - Breaking changes (field removal, rename, type change) will bump
api_versionand be announced in advance.
Changelog vs 2026-08-09
2026-08-09Additive:
first_purchase,plan_type,cycle_numbertimestamp_epoch; journeyevent_idis derived from the Event_idand remains stable across every delivery attempthad_cart;recovery_status/recovery_missing_detailsno longer strippedjourney_contact_capturedattribution.first|last.landing_url/referrer_urlwhen captured (omit when absent)
Removed:
products[].display_nameand unusedproduct_display_names
