Skip to main content

Message Variables

Personalize any journey message (email, SMS, push, WhatsApp, webhook) with Liquid placeholders. A variable that has no value renders as empty text - it is never an error.

The two event namespaces

Journeys give you two event namespaces, and the difference matters:

  • trigger - how they entered. The entry event, fixed for the whole run.
  • event - the latest thing they did. The most recent event that advanced the journey, such as the reply that resolved a Behavior Split. It starts equal to trigger, then re-binds each time a wait resolves.

So for "what the contact just replied," use event (or the reply aliases below) - not trigger.

User - default vs custom attributes

Default attributes - standard fields, written as {{ user.firstName }}:

VariableValue
user.firstName, user.lastNameName attributes
user.email, user.phoneContact fields
user.idJoryio's own contact id
user.externalIdYour external id
user.whatsappNameWhatsApp profile name synced to the contact

Custom attributes - anything else on the contact, under the custom namespace, e.g. {{ user.custom.plan }}:

VariableValue
user.custom.<attribute>Any custom attribute (e.g. user.custom.plan)
note

The older flat form {{ user.<attribute> }} still resolves for back-compat, but {{ user.custom.<attribute> }} is the recommended way to reference custom attributes - it keeps them visually distinct from default fields.

note

Default attribute names are reserved - you can't create a custom attribute with the same name as a default (e.g. email, country, firstName), so the two never collide and the picker never lists a name twice.

Inserting variables

The WhatsApp, SMS, push, and webhook editors have a + Personalize button that opens the Add Personalization picker. Pick a type, choose the attribute, add an optional default value, then Insert the generated snippet:

  • Latest event - the latest event that advanced the journey. Pick a source: Reply (WhatsApp) (the friendly reply.* aliases) or Custom event (event.*).
  • Trigger (entry) - how they entered. Pick a source: WhatsApp message or Custom event.
  • Default attributes - {{ user.firstName }}, {{ user.email }}, and the other first-class fields (the same list as the segment "default attribute" picker).
  • Custom attributes - your org's custom attributes only (defaults are listed under their own type, never both), inserted as {{ user.custom.<attribute> }}.
  • Product catalog - records from your synced store catalog via a product Selection, inserted as an assign tag such as {% assign products = 'featured' | products %} that you then loop over with {% for item in products %} … {% endfor %}.
  • Custom entity - records from a custom entity via a saved Selection, inserted as {% assign items = 'tv_series' | entity: 'latest' %} and looped the same way.

The optional default value appends a | default: "…" filter, so an empty attribute falls back to your text instead of rendering blank.

Email and in-app

The visual email and in-app editors have a Personalization button that opens the same picker in copy mode: build the snippet, copy it, and paste it wherever you want in your design. (Reply / Latest event / Trigger aren't offered there - those resolve only inside a journey.)

Trigger (entry event)

VariableValue
trigger.properties.<field>Any property the entry event carried
trigger.properties.content.textInbound text, if entry was a WhatsApp message

Event (latest advancing event) and reply aliases

After a Behavior Split that waits for a reply, event is that reply. The reply aliases are friendly shortcuts for inbound messages - they work the same for both a WhatsApp reply and an SMS reply. After an SMS Inbound Message Behavior Split, reply.text is the contact's latest inbound SMS text.

VariableAliasValue
event.properties.content.textreply.textThe reply text
event.properties.typereply.typeInbound message type (text, image, button…)
event.properties.profile.namereply.profile.nameWhatsApp profile name
event.name-The event name
event.properties.<field>-Any other captured property

Example - a WhatsApp reply after a Behavior Split

Thanks {{ reply.profile.name | default: "there" }} - you said
"{{ reply.text }}". We'll follow up shortly.
tip

Use trigger.* for how they entered and event.* / reply.* for what they just did. For a scheduled-entry journey, trigger has no inbound content - the reply after a split lives on event / reply.