- Triggers Journey entry, or
- Matches a Wait Until condition inside the Journey
How Custom Event personalization works
Add event properties to your Journey messages following these steps:Send a Custom Event with properties
Example Custom Events payload:
JSON
Reference event properties in your Journey message templates
Use Liquid syntax to access event properties.
Common Liquid access patterns
| What you want | Liquid | Output |
|---|---|---|
| Event name | {{ journey.first_event.name }} | purchase |
| Property | {{ journey.first_event.properties.item }} | Blue Sweater |
| Nested properties | {{ journey.first_event.properties.details.first.manufacturer }} | Company A |
| Property with special characters | {{ journey.last_event.properties["order status"] }} | pending |
| Timestamp | {{ journey.last_event.timestamp | date: "%B %d, %Y at %I:%M %p" }} | October 21, 2025 at 07:09 PM |
Nested Liquid access patterns
You can also access nested properties using dot and bracket notation:Liquid
Create a Journey
Setup the Journey to use the Custom Event as the entry rule and/or Wait Until condition.
- See Journeys settings for Entry rules.
- See Journeys actions for Wait Until conditions.
Event property storage rules
- You can use multiple events in your Journey by combining entry rules and Wait Until steps.
- Maximum: 100 stored event properties per user per Journey instance (oldest dropped).
- Event properties are stored per user, per Journey instance.
- Events sent before entry are not accessible.
- Event properties are cleared when the user exits the Journey.
Custom Event Liquid reference
Use these objects to access stored events inside the Journey.journey.first_event
The first stored event for this Journey instance.
- If using a Custom Event Entry Rule, than this is the event that caused Journey entry.
- If not using a Custom Event Entry Rule, then this is the first event stored by matching a Wait Until condition.
Liquid
journey.last_event
The most recent stored event for this Journey instance.
- If only one event is stored,
first_eventandlast_eventreturn the same thing.
Liquid
journey.event.EVENT_NAME
The most recent stored event with a specific name.If your event name includes spaces or special characters, use bracket notation.Example Event:
- Replace
EVENT_NAMEwith your event name (e.g.,purchase). - If the same event name is used multiple times, this returns the most recent instance.
Liquid
"name": "order status"Liquid
All stored events for this Journey instance, in the order they were stored.
- Use for loops to iterate over them.
Liquid
journey.first_eventis shorthand forjourney.all_events[0].journey.last_eventis shorthand for the most recent event in the array.
Example: Abandoned cart templates using Custom Events
This example shows how to personalize abandoned cart messages using Custom Events. It builds on the Abandoned Cart tutorial. Example Custom Event set:JSON
Email template
This example shows how to build an email template that displays:- The cart item count
- Each product with image, name, quantity, and price using a for-loop
- A button that links to the customer’s unique cart URL

Create the email template
Navigate to Messages > Templates > New Email Template and open the Drag & Drop Editor.
Add the layout structure
Create five rows:
- Rows 1, 2, and 4: one column with a Paragraph block
- Row 3: four columns with HTML | Paragraph | Paragraph | Paragraph
- Row 5: one column with a Button block

Display the item count
In row 1, add:For better grammar, you could use a conditional to say “1 item” vs “2 items”, but for abandoned cart emails, plural is usually acceptable.
Liquid
Liquid
Start the for-loop
Use a for-loop to repeat the product display row for each cart item.In row 2 (loop start), add:What this does:
Liquid
- Begins a loop that iterates over each object in the
cartarray - Creates a temporary variable
productrepresenting the current item - Everything between
{% for %}and{% endfor %}repeats once per cart item - You can name
productanything (e.g.,item,cartItem)—just stay consistent
Display product details
This 4-column row shows image, name, quantity, and price. Because it’s inside the loop, it repeats for every cart item.In row 3 (product details), configure:Column 1 - HTML block (product image):Columns 2–4 - Text blocks (product name, quantity, price):
- Column 2:
{{product.product_name}} - Column 3:
{{product.product_quantity}} - Column 4:
{{product.product_price}}
- On first iteration,
product= first object in cart array {{product.product_image}}gets the first item’s image- On second iteration,
product= second object - Row repeats automatically for all cart items
End the for-loop
Close the loop to mark where repetition stops.In row 4 (loop end), add:
Liquid
Every
{% for %} must have a matching {% endfor %}. Missing this will break email rendering.Test the template
- Add the template to a blank Journey and set the entry rule to a Custom Event.
- Enabling the Journey and entering yourself into it via the Custom Event API.
- Verify the data displays correctly.
Success! Now you can apply your own styling to the template. See Design emails with drag-and-drop.
Push template
Push notifications have limited space, so display one item and mention the total count. Message field: Display item and count with correct grammar using conditional statements.Liquid
Liquid
Liquid

Success! You can now create more templates and use them in the Abandoned Cart Journey.
Troubleshooting & best practices
Common mistakes:| Mistake | Why it fails | Correct syntax |
|---|---|---|
{{ journey.first_event.item }} | Missing .properties | {{ journey.first_event.properties.item }} |
{{ journey.event.purchase.item }} | Missing .properties | {{ journey.event.purchase.properties.item }} |
{{ journey.first_event.properties.Item }} | Wrong case (should be item) | {{ journey.first_event.properties.item }} |
{{ event.properties.item }} | Missing journey. prefix | {{ journey.first_event.properties.item }} |
- Always test templates before going live
- Use default filters for optional properties
- Validate event schema matches template expectations
Related pages
Message personalization
Overview of all personalization options in OneSignal, including when to use Custom Events vs other methods.
Custom Events
Complete guide to implementing and sending Custom Events via SDK or API.
Journeys overview
Learn how to build automated messaging workflows with triggers, conditions, and actions.
Journey settings
Configure event-triggered entry rules and Journey behavior.
Wait Until actions
Use Wait Until nodes to store additional events during Journey progression.
Using Liquid syntax
Complete Liquid reference with filters, conditionals, loops, and string manipulation.
Templates
Create and manage reusable message templates for use in Journeys.
Need help?Chat with our Support team or email
[email protected]Please include:- Details of the issue you’re experiencing and steps to reproduce if available
- Your OneSignal App ID
- The External ID or Subscription ID if applicable
- The URL to the message you tested in the OneSignal Dashboard if applicable
- Any relevant logs or error messages
