Journeys

By developer@lovi… , 25 April 2026

A Journey is an automated multi-step workflow that runs for a customer when a business event occurs. You define what a journey does using a machineConfig — a JSON object that lists the steps to execute in order.

When the configured trigger event fires and the journey is active, the system starts a new execution and runs each step in sequence.

Contents


Trigger events

The triggerEvent field controls when an active journey starts running for a customer. It is set at creation and cannot be changed afterwards.

EventDescription
manualTriggered explicitly via the /run endpoint — not fired by any system event.
order.createdA new customer order was placed.
order.updatedAn existing order was modified.
order.closedA customer order was finalised.
campaign.public.startedA public campaign transitioned to active status.
campaign.private.startedA private campaign transitioned to active status.
account.createdA new loyalty account was registered.
account.updatedAn existing loyalty account was modified.
account.tier.upgradedAn account moved to a higher loyalty tier.
account.tier.downgradedAn account moved to a lower loyalty tier.
account.tier.extendedAn account's tier expiry was extended.
account.classification.became_vipAn account was classified as VIP.
account.classification.became_newAn account was classified as new.
account.classification.became_activeAn account was classified as active.
account.classification.became_inactiveAn account was classified as inactive.
account.classification.became_slipping_awayAn account was classified as slipping away.
account.classification.became_churnedAn account was classified as churned.

Defining workflow steps (machineConfig)

The machineConfig field on a journey defines the steps that run for each customer. You set it when creating a journey or by updating the journey later. Until machineConfig is set the journey will complete immediately with no actions taken.

A machineConfig has two required fields:

  • initial — the name of the first step to run (you choose the name).
  • states — an object where each key is a step name. Each step has a meta object describing what to do, and an on object describing what step to go to next.

The last step must be a final state — an entry with "type": "final" and no meta. When the machine reaches a final state the execution is marked complete.

Minimal example — send one email then finish

{
  "initial": "send_welcome",
  "states": {
    "send_welcome": {
      "meta": { "type": "send_email", "templateId": 5 },
      "on": { "DONE": "done" }
    },
    "done": { "type": "final" }
  }
}

Reading this step by step:

  1. Start at send_welcome.
  2. Send email using template 5.
  3. When done, move to done.
  4. done is a final state — the execution completes.

Multi-step example — wait 3 days then send SMS

{
  "initial": "wait",
  "states": {
    "wait": {
      "meta": { "type": "wait_for_duration", "days": 3 },
      "on": { "ELAPSED": "send_sms" }
    },
    "send_sms": {
      "meta": { "type": "send_sms", "templateId": 12 },
      "on": { "DONE": "done" }
    },
    "done": { "type": "final" }
  }
}

Note that wait_for_duration uses ELAPSED (not DONE) as its transition event. All other step types use DONE.

Branching example — condition check

A condition step evaluates an attribute against a value and branches on the result. Use it when you want different steps to run depending on the customer's state.

{
  "initial": "check_vip",
  "states": {
    "check_vip": {
      "meta": {
        "type": "condition",
        "attribute": "account.classification",
        "operator": "eq",
        "value": "vip"
      },
      "on": {
        "TRUE": "send_vip_email",
        "FALSE": "send_standard_email"
      }
    },
    "send_vip_email": {
      "meta": { "type": "send_email", "templateId": 20 },
      "on": { "DONE": "done" }
    },
    "send_standard_email": {
      "meta": { "type": "send_email", "templateId": 21 },
      "on": { "DONE": "done" }
    },
    "done": { "type": "final" }
  }
}

Step types

Each step's behaviour is controlled by its meta object. The type field inside meta determines what the step does.

send_email

Sends an email to the customer using a Communication Template.

"meta": { "type": "send_email", "templateId": 5 }

Transition event: DONE

send_sms

Sends an SMS to the customer using a Communication Template.

"meta": { "type": "send_sms", "templateId": 12 }

Transition event: DONE

send_push_notification

Sends a push notification to the customer's mobile device using a Communication Template.

"meta": { "type": "send_push_notification", "templateId": 8 }

Transition event: DONE

create_campaign

Creates a private campaign for this specific customer by cloning a campaign template. The campaignId is the ID of the template campaign to clone — the new campaign is assigned privately to the customer.

"meta": { "type": "create_campaign", "campaignId": 55 }

Transition event: DONE

wait_for_duration

Pauses execution for a fixed amount of time before continuing. Specify the duration using any combination of time unit fields: years, months, weeks, days, hours, minutes, seconds.

"meta": { "type": "wait_for_duration", "days": 3 }
"meta": { "type": "wait_for_duration", "hours": 48 }
"meta": { "type": "wait_for_duration", "days": 1, "hours": 12 }

Transition event: ELAPSED (not DONE)

wait_for_condition

Pauses execution and re-evaluates every 5 minutes until the customer matches the specified targeting segments. Use include to require the customer to be in at least one of the listed segments. Use exclude to require the customer to not be in any of the listed segments.

"meta": { "type": "wait_for_condition", "include": [12, 15] }
"meta": { "type": "wait_for_condition", "exclude": [3] }
"meta": { "type": "wait_for_condition", "include": [12], "exclude": [3] }

The numbers are targeting segment IDs. Transition event: DONE

condition

Evaluates an attribute on the customer and immediately branches based on the result. Use this when you want to take a different path depending on the customer's classification, tier, or other attribute.

"meta": {
  "type": "condition",
  "attribute": "account.classification",
  "operator": "eq",
  "value": "vip"
}

Supported operators: eq (equals), ne (not equals), in (value is in a list), exists (attribute is set). Transition events: TRUE and FALSE — both must be handled in on.


Journey fields

FieldTypeNotes
idintegerRead-only.
placeIdintegerRead-only. Set from URL parameter.
userIdintegerRead-only. Set from the authenticated user.
createdinteger (Unix seconds)Read-only.
updatedinteger (Unix seconds)Read-only.
namestringRequired on create. Editable.
descriptionstringOptional. Editable.
triggerEventstring (enum)Required on create. Cannot be changed after creation. See trigger events.
machineConfigobjectDefines the workflow steps. Optional on create — the journey will complete immediately if not set. Editable. See machineConfig.
activebooleanDefault false. Use the /start and /stop endpoints to toggle. Manual journeys are always active.

Endpoints

All endpoints require an Api-Key header with a valid manager API key for the place.

Request bodies use snake_case keys (e.g. trigger_event, machine_config). Responses use camelCase keys wrapped under "node.journey_definition", keyed by ID.

MethodPathDescription
GET/places/:placeId/journeysList all journeys for a place (paginated).
GET/places/:placeId/journeys/:journeyIdGet a single journey by ID.
POST/places/:placeId/journeysCreate a journey.
PUT/places/:placeId/journeys/:journeyIdUpdate a journey (including setting or updating machineConfig).
DELETE/places/:placeId/journeys/:journeyIdDelete a journey and all its executions.
POST/places/:placeId/journeys/:journeyId/startSet active = true. The journey will now fire when its trigger event occurs.
POST/places/:placeId/journeys/:journeyId/stopSet active = false.
POST/places/:placeId/journeys/:journeyId/runManually trigger a manual journey for all eligible customers. Accepts include_segment_ids and exclude_segment_ids arrays to filter which customers are targeted.

Examples

Create a journey and define its steps

Step 1 — Create the journey:

POST /places/42/journeys
{
  "name": "Welcome new members",
  "trigger_event": "account.created",
  "description": "Send a welcome email when someone joins the loyalty programme."
}
200 OK
{
  "node.journey_definition": {
    "101": {
      "id": 101,
      "placeId": 42,
      "name": "Welcome new members",
      "description": "Send a welcome email when someone joins the loyalty programme.",
      "triggerEvent": "account.created",
      "machineConfig": null,
      "active": false,
      "created": 1714000000,
      "updated": 1714000000
    }
  }
}

Step 2 — Set the workflow steps via machineConfig:

PUT /places/42/journeys/101
{
  "machine_config": {
    "initial": "send_welcome",
    "states": {
      "send_welcome": {
        "meta": { "type": "send_email", "templateId": 5 },
        "on": { "DONE": "done" }
      },
      "done": { "type": "final" }
    }
  }
}

Step 3 — Start the journey so it fires when new accounts are created:

POST /places/42/journeys/101/start

Multi-step win-back journey

This journey fires when a customer is classified as slipping away. It waits 2 days, sends a win-back email, waits another 3 days, then sends an SMS reminder.

Step 1 — Create the journey:

POST /places/42/journeys
{
  "name": "Win-back: slipping away",
  "trigger_event": "account.classification.became_slipping_away"
}

Returns journey ID 102.

Step 2 — Set the workflow:

PUT /places/42/journeys/102
{
  "machine_config": {
    "initial": "wait_2_days",
    "states": {
      "wait_2_days": {
        "meta": { "type": "wait_for_duration", "days": 2 },
        "on": { "ELAPSED": "send_email" }
      },
      "send_email": {
        "meta": { "type": "send_email", "templateId": 30 },
        "on": { "DONE": "wait_3_days" }
      },
      "wait_3_days": {
        "meta": { "type": "wait_for_duration", "days": 3 },
        "on": { "ELAPSED": "send_sms" }
      },
      "send_sms": {
        "meta": { "type": "send_sms", "templateId": 31 },
        "on": { "DONE": "done" }
      },
      "done": { "type": "final" }
    }
  }
}

Step 3 — Start the journey:

POST /places/42/journeys/102/start

Manual journey with segment filtering

A manual journey does not fire automatically — you trigger it yourself via /run. It is useful for one-off campaigns where you want to decide exactly who gets contacted. Manual journeys are always active and do not need to be started.

Step 1 — Create the journey:

POST /places/42/journeys
{
  "name": "One-off promo blast",
  "trigger_event": "manual"
}

Returns journey ID 103.

Step 2 — Set the workflow:

PUT /places/42/journeys/103
{
  "machine_config": {
    "initial": "send_push",
    "states": {
      "send_push": {
        "meta": { "type": "send_push_notification", "templateId": 9 },
        "on": { "DONE": "done" }
      },
      "done": { "type": "final" }
    }
  }
}

Step 3 — Run it for VIP customers only (segment ID 4), excluding churned customers (segment ID 6):

POST /places/42/journeys/103/run
{
  "include_segment_ids": [4],
  "exclude_segment_ids": [6]
}
200 OK
{ "triggered": 38 }

Setting up a journey via AI Recommendation

The Recommendations system can create a journey alongside its communication templates atomically in a single accept operation. Include machineConfig directly in the journey recommendation's data field. For template steps where the template is also being created as part of the same recommendation tree, set a localRef string in the template recommendation's data (e.g. "localRef": "welcome_email") and reference it in the journey's machineConfig using templateRef (e.g. "templateRef": "welcome_email") instead of templateId. The system resolves all references after the entire tree is accepted, regardless of the order or position of recommendations in the tree. See the Recommendations developer guide for full examples.

Comments