Journey Abandoned

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

FieldGuarantee
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

{
  "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

Intake 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"
  }
}
FieldTypeAlways?Notes
had_cartboolean✅true when a pending ecommerce order existed at emit time. Gate recovery on this.
first_purchaseboolean⚠️Present only when had_cart is true. Omitted on intake drop-off.
ecommerce_order_idstring⚠️Present only when a pending order existed.
recovery_statusstring✅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_detailsstring[]✅Details still required before recovery can complete. Empty when none.
recovery_url, recovery_url_expires_atstring⚠️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.
productsarray✅{ product_variation_id, quantity, unit_price_cents, is_addon } only. No display_name. Template-derived when there is no order.
attributionobject⚠️Sourced from the order when present, otherwise from the journey. See Attribution Passthrough.
intake_form_instance_idsstring[]⚠️AffiliateIntakeFormInstance ids (aifi::…) for custom intake forms on this journey (pre- and post-checkout). Omitted when no instances exist.