Fires when a started journey with a patient is inactive for 6 hours (JOURNEY_ABANDON_THRESHOLD_HOURS). The eventType is journey_abandoned. Category: JOURNEY.
This is an inactivity signal, not a guaranteed lost sale. Gate cart recovery on had_cart. The event fires once per journey. Deduplicate on event_id.
v1 and v2 payloads are identical for journey events. These are journey-native events built on the flat, sanitized payload shape — there is no separate raw-envelope form.
Money fields are integer cents: 84900 = $849.00. plan_type / products / cents still come from the journey template when the patient never reached checkout. first_purchase and ecommerce_order_id are present only when a pending order existed (had_cart: true). Attribution is still sourced from the journey when there is no order.
Field guarantees
| Field | Guarantee |
|---|---|
event_type, event_id, api_version, timestamp, timestamp_epoch, journey_id, affiliate_id, template_id, patient_marketing_consent, is_returning_patient, total_value_cents, currency, products, plan_type, had_cart, recovery_status | ✅ Always present |
template_display_name, journey_type, patient_id, patient_external_id, started_at, coupon_code, ecommerce_order_id, first_purchase, attribution, intake_form_instance_ids | ⚠️ Conditional — omitted when the underlying data is absent. first_purchase and ecommerce_order_id require a pending order. |
recovery_url, recovery_url_expires_at | ⚠️ Present when a recovery link can be issued |
recovery_missing_details | ✅ Array (may be empty) |
Payload (v1 and v2) — had_cart: true
had_cart: true{
"event_type": "journey_abandoned",
"event_id": "evt::example",
"api_version": "2026-08-17",
"timestamp": "2026-05-26T12:00:00.000Z",
"timestamp_epoch": 1779796800,
"journey_id": "jour::example",
"ecommerce_order_id": "eco::example",
"affiliate_id": "aff::example",
"template_id": "jtmp::example",
"template_display_name": "Wellness Program",
"journey_type": "ONE_TIME",
"patient_id": "pat::example",
"patient_external_id": "ext-patient-123",
"patient_marketing_consent": true,
"is_returning_patient": false,
"first_purchase": true,
"plan_type": "one_time",
"intake_form_instance_ids": ["aifi::example"],
"started_at": "2026-05-26T06:00:00.000Z",
"total_value_cents": 4999,
"currency": "usd",
"products": [
{
"product_variation_id": "epv::example",
"quantity": 1,
"unit_price_cents": 4999,
"is_addon": false
}
],
"coupon_code": "WELCOME20",
"had_cart": true,
"recovery_status": "recovery_available",
"recovery_missing_details": [],
"recovery_url": "https://app.example.com/orders/example?token=example",
"recovery_url_expires_at": "2026-05-27T12:00:00.000Z",
"attribution": {
"first": {
"captured_at": "2026-01-04T10:22:31.000Z",
"landing_url": "https://shop.example.com/get-started",
"params": {
"utm_source": "google",
"gclid": "Cj0KCQexample"
}
},
"touch_count": 1,
"visitor_id": "550e8400-e29b-41d4-a716-446655440000"
}
}Payload (v1 and v2) — had_cart: false
had_cart: falseIntake drop-off: the 6-hour timer fired before checkout. Attribution still comes from the journey. first_purchase and ecommerce_order_id are omitted.
{
"event_type": "journey_abandoned",
"event_id": "evt::example",
"api_version": "2026-08-17",
"timestamp": "2026-05-26T12:00:00.000Z",
"timestamp_epoch": 1779796800,
"journey_id": "jour::example",
"affiliate_id": "aff::example",
"template_id": "jtmp::example",
"template_display_name": "Wellness Program",
"journey_type": "ONE_TIME",
"patient_id": "pat::example",
"patient_external_id": "ext-patient-123",
"patient_marketing_consent": true,
"is_returning_patient": false,
"plan_type": "one_time",
"started_at": "2026-05-26T06:00:00.000Z",
"total_value_cents": 4999,
"currency": "usd",
"products": [
{
"product_variation_id": "epv::example",
"quantity": 1,
"unit_price_cents": 4999,
"is_addon": false
}
],
"had_cart": false,
"recovery_status": "recovery_unavailable",
"recovery_missing_details": [],
"attribution": {
"first": {
"captured_at": "2026-01-04T10:22:31.000Z",
"landing_url": "https://shop.example.com/get-started",
"params": {
"utm_source": "google",
"gclid": "Cj0KCQexample"
}
},
"touch_count": 1,
"visitor_id": "550e8400-e29b-41d4-a716-446655440000"
}
}| Field | Type | Always? | Notes |
|---|---|---|---|
had_cart | boolean | ✅ | true when a pending ecommerce order existed at emit time. Gate recovery on this. |
first_purchase | boolean | ⚠️ | Present only when had_cart is true. Omitted on intake drop-off. |
ecommerce_order_id | string | ⚠️ | Present only when a pending order existed. |
recovery_status | string | ✅ | Whether a recovery link can be issued. Use recovery_available only with a pending cart (had_cart: true) and typically recovery_url. Intake drop-off (had_cart: false) is non-recoverable (recovery_unavailable). |
recovery_missing_details | string[] | ✅ | Details still required before recovery can complete. Empty when none. |
recovery_url, recovery_url_expires_at | string | ⚠️ | Cart-recovery link and expiry. Treat recovery_url as a credential — its token grants access to the patient's checkout session. Don't log or persist it beyond use, don't expose it outside the recovery email/SMS, and honor recovery_url_expires_at. |
products | array | ✅ | { product_variation_id, quantity, unit_price_cents, is_addon } only. No display_name. Template-derived when there is no order. |
attribution | object | ⚠️ | Sourced from the order when present, otherwise from the journey. See Attribution Passthrough. |
intake_form_instance_ids | string[] | ⚠️ | AffiliateIntakeFormInstance ids (aifi::…) for custom intake forms on this journey (pre- and post-checkout). Omitted when no instances exist. |
