Fires once per journey when that journey is first linked to a patient that has persisted contact. The eventType is journey_contact_captured. Category: JOURNEY.
This is the lead event. Contact exists; there is no purchase yet. Deduplicate on event_id. A second attach is a no-op.
It does not fire on OTP begin (that path only looks up email/phone in memory). Emit sites:
- Patient attach after existing-user OTP or post-registration.
- Journey create when
patientis already set on the body (attach never runs). - Safety net: immediately before
journey_completed.
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.
Field guarantees
| Field | Guarantee |
|---|---|
event_type, event_id, api_version, timestamp, timestamp_epoch, journey_id, affiliate_id, template_id, patient_id, patient_marketing_consent, is_returning_patient | ✅ Always present |
template_display_name, journey_type, patient_external_id, started_at, attribution, intake_form_instance_ids | ⚠️ Conditional — omitted when the underlying data is absent. patient_external_id is omitted when the patient has no external identifier. |
No money fields (total_value_cents, products, plan_type) and no first_purchase — there is no order yet. No raw email or phone. Hashed identifiers (email_sha256 / phone_sha256) are not shipped.
Payload (v1 and v2)
{
"event_type": "journey_contact_captured",
"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": false,
"is_returning_patient": false,
"started_at": "2026-05-26T12:00:00.000Z",
"intake_form_instance_ids": ["aifi::example"],
"attribution": {
"first": {
"captured_at": "2026-01-04T10:22:31.000Z",
"landing_url": "https://shop.example.com/get-started",
"referrer_url": "https://google.com/",
"params": {
"utm_source": "google",
"gclid": "Cj0KCQexample"
}
},
"last": {
"captured_at": "2026-01-04T10:22:31.000Z",
"params": {
"utm_source": "google",
"gclid": "Cj0KCQexample"
}
},
"touch_count": 1,
"visitor_id": "550e8400-e29b-41d4-a716-446655440000"
}
}| Field | Type | Always? | Notes |
|---|---|---|---|
event_id | string | ✅ | Event document _id. Stable across retries — use as the dedup key. |
timestamp_epoch | integer | ✅ | Unix seconds for timestamp. Do not replace ISO timestamp. |
patient_id | string | ✅ | Always present — this event fires only after the journey is linked to a patient. Identity is this id, not email/phone. |
first_purchase, plan_type, money fields | — | ⛔ | Never sent. No order exists at first contact. |
attribution | object | ⚠️ | See Attribution Passthrough. landing_url / referrer_url omitted when absent. |
template_display_name | string | ⚠️ | Name journeys neutrally (no drug or condition names). |
intake_form_instance_ids | string[] | ⚠️ | AffiliateIntakeFormInstance ids (aifi::…) for custom intake forms on this journey (pre- and post-checkout). Omitted when no instances exist yet. |
