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-09" (Journey events: "2026-08-06") |
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-09",
"timestamp": "2026-05-30T14:00:00.000Z",
"affiliate_id": "aff::xyz",
"order_id": "order::abc"
}The api_version value for most v2 envelopes is 2026-08-09. It identifies the payload schema and will be bumped if a breaking change is ever made. Journey-native events are the exception and use "2026-08-06" (see Versioning guarantees).
timestampis the delivery-attempt time, not the event's creation time — it is regenerated on every retry. Don't use it 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, regardless of category, also carries four patient-identity fields (patient_id, patient_external_id, patient_marketing_consent, is_returning_patient) added by a separate, always-on enrichment step that isn't governed by the category allowlist at all.
| Category | Allowed fields |
|---|---|
| ORDER | order_id, order_status, case_status, case_id, ecommerce_order_id, journey_id, commerce_status, product_variation_ids, product_display_names, 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 |
| PRESCRIPTION | prescription_id, prescription_status, pharmacy_id, order_id, commerce_status, product_variation_ids, product_display_names, visit_id, video_requested, reason |
| SHIPPING | order_id, prescription_id, shipment_status, commerce_status, tracking_number, tracking_url, shipping_carrier |
| PHARMACY | prescription_id, pharmacy_id, pharmacy_status, order_id, commerce_status, reason |
| PAYMENT | order_id, total_value_cents, currency, payment_status, consultation_id |
| 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 |
| 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, commerce_status, product_variation_ids, product_display_names, subscription_status, attribution |
| COMMERCE | ecommerce_order_id, subscription_id, subscription_status, commerce_status, total_value_cents, currency, payment_status, product_variation_ids, product_display_names, journey_id, order_id, refund_amount_cents, refund_type |
| 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.
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 59 event types, of which 56 are publicly subscribable by affiliates. Two are internal-only (never delivered to affiliate webhooks), and one additional public event (pharmacy_rejected_prescription) is defined but not yet implemented — it is excluded from subscription (56 + 2 + 1 = 59). All 56 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
- Most v2 envelopes use
api_version: "2026-08-09". This replaced"2026-05-24"when marketing attribution was extended beyond journey events — see Attribution Passthrough. The change is additive: consumers that ignore unknown fields need no action. - Exception — Journey-native events (
journey_checkout_started,journey_completed,journey_abandoned) always useapi_version: "2026-08-06", including when the optionalattributionkey is omitted. See Attribution Passthrough. - Within a given
api_version, fields are additive (never removed or renamed). Adding a significant new block such asattributionmay still bumpapi_versionso documentation and consumers can target the schema explicitly — as with the Journey bump to"2026-08-06". - Breaking changes (field removal, rename, type change) always bump
api_versionand are announced in advance.
