Creating a Canvas
Canvas is Joryio's visual journey builder. Use it to create automated, multi-step workflows that respond to user behavior in real time - onboarding flows, abandoned cart sequences, trial conversions, win-back campaigns, and more.
The internal route and entity name is still canvas, but the user-facing name is User Journey. The two terms are interchangeable in the docs.
What you can build
- Onboarding flows - guide new users through your product
- Abandoned cart - remind users to complete a purchase
- Trial conversion - nurture trial users to paid plans
- Re-engagement - win back inactive users
- Upsell sequences - encourage upgrades
- Cross-channel orchestration - coordinate email, SMS, push, WhatsApp, and in-app
The six-tab wizard
Canvas uses a numbered six-tab wizard. The header shows a Stepper with subtitles, connector lines, green checks on completed steps, and an orange ! badge on any tab you visited but left with validation errors. Continue, Save Draft, and Publish all live in the header - there's no bottom nav row.
Each tab transition auto-saves your work as status='draft'. The first save creates the record; subsequent saves PATCH. You can leave and come back without losing anything.
| # | Tab | What you configure |
|---|---|---|
| 1 | Details | Name, description, status, tags. |
| 2 | Delivery | Send type and trigger configuration. |
| 3 | Audience | Mode chooser → filter builder → re-entry policy → exit criteria. |
| 4 | Conversion | Primary + secondary conversion events. |
| 5 | Journey editor | The full-screen node editor. |
| 6 | Summary | Read-only review before publish. |
Open the wizard at Canvas → New User Journey.
Tab 1: Details
- Name (required) and optional Description.
- Status -
draft/active/paused/archived. Most journeys start asdraftand flip toactiveon publish. - Tags - team-scoping tags that double as suppression-list bypass tags (same model as campaigns). Type a tag, press Enter, and it commits as a removable pill below the input.
Tab 2: Delivery
Pick a send type - required, no silent default:
| Send type | When users enter |
|---|---|
immediate | Once, right after publish, for every user matching the audience. |
scheduled | Once at a chosen date/time, for every user matching at that moment. |
recurring | On a schedule (daily / weekly / monthly / custom cron). Audience re-evaluated each firing. |
trigger | When a trigger fires - event, entity change, WhatsApp inbound, inbound SMS, or API call. Audience gates entry per user. |
For trigger mode, pick a trigger type (also required):
-
Built-in - Joryio's own high-value triggers, surfaced as a curated list so you don't have to hunt for them in every tracked event:
- Back in stock / Restock - a product or item a user showed interest in is available again. Pick the source: Product catalog (
back_in_stock, auto-emitted per user from your synced store - Shopify / WooCommerce / Magento) or Custom entity (entity_back_in_stock, from an Entity Relationship Alert - e.g. a course, episode, or appointment slot). - Price drop (
price_drop) - a product a user showed interest in dropped in price past your threshold, from your synced product catalog.
Each is stored as a normal event trigger, and you can add per-property conditions right in the picker (for example, only a specific category, or below a price threshold).
- Back in stock / Restock - a product or item a user showed interest in is available again. Pick the source: Product catalog (
-
Event tracked - fires when a named event is tracked for the user.
-
Entity changed - fires on
created/updated/deleted/ field-change of a custom entity. -
WhatsApp inbound - fires when the user sends a matching inbound WhatsApp message.
-
Inbound SMS - fires when a known contact texts in. Filter by SMS account, message type, and conditions on Message Text / Message Type (equals, contains, starts with, etc.). Inbound SMS is captured by the number's inbound webhook. Only contacts already in your audience with that phone number are enrolled; replies from unknown numbers are stored but don't auto-create a contact.
-
Attribute changed - fires when a user attribute changes value.
-
Subscription status - fires when a channel / list subscription status changes.
-
API (manual entry) - no automatic listener. Users are entered via
POST /api/canvas/:id/enter.
Additional delivery settings:
- Quiet-time override - ignore quiet hours / days / holidays for this journey.
- Touching-rules override - bypass workspace-level frequency caps. Reserved for transactional / critical messages.
Tab 3: Audience
The Audience tab opens with a mode chooser - same two tiles as the campaign wizard:
- Targeted audience (RECOMMENDED - green badge) - the filter builder.
- Send to everyone - broadcast to your whole workspace.
Once you pick a mode, the journey wizard mirrors the campaign wizard exactly: v2 filter builder chrome (numbered colored badges per group, inline AND/OR ConnectorChip between rows + groups, + Add condition / + Add filter group dashed buttons, confirm-on-delete for non-empty groups, live count pills per group). The right rail shows audience size with a green Live pill, percentage of workspace, 4px progress bar, and per-channel breakdown.
Event and attribute pickers are searchable comboboxes with a + Create event/attribute "<query>" row when nothing matches.
Re-entry policy
What happens when a user who already has an active execution triggers the journey again:
| Policy | Behavior | minIntervalMinutes label |
|---|---|---|
block (default) | Don't allow re-entry. | "Cooldown after exit (minutes)" - N minutes must pass between the user's last exit and the next entry. |
restart | Exit the old execution, start a new one. | (field hidden - restart already kills the old execution) |
parallel | Allow multiple concurrent executions per user. | "Minimum time between entries (minutes)" - throttle from last entry. |
For parallel mode, also set Max concurrent (1-10).
Exit Criteria
Define when a user should exit a running journey early. Uses the same filter-groups builder as Audience, with the include block only (no exclude - layering an exclude on top of "remove users matching X" reads as a confusing double-negative).
A scheduler sweep runs every five minutes: for each active journey with Exit Criteria defined, it resolves matching users, intersects with currently-active executions, and transitions them to exited with exitReason='exit_criteria_matched'.
Typical uses:
- Stop when the user converts - primary-conversion event filter.
- Remove on unsubscribe - channel-subscription or list-membership filter.
- Skip users who complete the goal elsewhere - behavioral or canvas-execution filter.
Tab 4: Conversion
- Primary conversion event - the event name that marks the user as converted. Optional property conditions refine the match.
- Attribution window - hours after entry to still credit a conversion.
- Attribution model -
first_touch/last_touch/linear. - Secondary conversion events - additional goals tracked alongside primary (e.g.
added_to_cart,viewed_pricing). Each can have an optional label.
Tab 5: Journey editor
Clicking this tab jumps to the full-screen node editor at /canvas/:id?from=wizard. The trigger node is already seeded with your Delivery-tab configuration and is locked - you can't edit or delete it from inside the editor. To change how the journey starts, return to the Delivery tab.
Available nodes
Message nodes
- Email - send an email
- SMS - send a text message
- Push - send a push notification
- WhatsApp - send a WhatsApp template
- In-app - display an in-app message
- Webhook - call an external API
Logic nodes
- Delay - wait for a fixed period (e.g. 1 day, 3 hours)
- Wait Until - wait for an event, attribute change, or specific date
- Branch - split users based on conditions
- Filter - only let users through if they match criteria
- Exit - end the journey
Experiment nodes
- A/B Test - test message variants
- Random Split - randomly assign users to paths
The "Back to wizard" action returns to the Summary tab so you can review and publish.
Tab 6: Summary
Read-only review of everything configured. Validation errors from earlier tabs surface here as a banner. The Publish button is disabled until the list is empty and the journey has at least one saved node.
Example: trial conversion journey
A simple flow that nurtures trial users toward paid:
[Trigger: Event "trial_started"]
↓
[Delay: 1 day]
↓
[Email: Welcome + getting started guide]
↓
[Delay: 3 days]
↓
[Branch: Has used Feature X?]
├─ Yes → [Email: Advanced tips]
└─ No → [Email: How to use Feature X]
↓
[Delay: 7 days]
↓
[Filter: Still on trial?]
↓
[Email: Trial ending soon - 20% off]
↓
[Wait Until: Converts OR trial ends]
├─ Converted → [Email: Welcome to Premium]
└─ Timeout → [Exit]
Run lifecycle: completed vs. exited
Each user that enters a journey is a separate run. A run lives in one of four states:
| Status | Meaning |
|---|---|
| active | The user is currently moving through the journey. |
| completed | The user reached an Exit node naturally - the happy path finished. Set when the runtime hits an Exit step. |
| exited | The user was kicked out of the journey before reaching an Exit node. The run row stamps an exitReason field with one of the cases below. |
| failed | A node errored hard (e.g. a webhook returned 500 with no fallback). |
Why exited runs exist
A run can be force-ended in four situations:
exit_criteria_matched- the run matched a global Exit Criteria filter (e.g. user unsubscribed, became a paying customer). A five-minute scheduler sweep pulls matching users out early.reentry_restart- the user re-entered the journey while a prior run was still active and the re-entry policy is restart. The prior run is force-exited so the new run can start clean.force_exited_on_publish- an admin published a new version with the force exit existing users transition mode. All in-flight users on the previous version are exited.version_orphaned_cleanup- the user was stuck on a version that was archived. After an hour of inactivity on an orphaned version, the runtime cleans them up.
Long-running journeys (multi-week, multi-month re-engagement flows, etc.) are explicitly supported - there's no global "kill the run after N hours" cap. If you need to bound a single step's wait, use the per-step Delay node's duration; if you need to remove users when their state changes, use Exit Criteria.
Why the distinction matters for segmentation
The segment builder's Canvas Journey filter has separate operators for each state - they describe different cohorts:
- Completed canvas = your conversion / success cohort. Users who finished the flow naturally.
- Exited canvas = your churn-from-the-flow cohort. Users who started but didn't finish - useful for retargeting (e.g. "users who exited the activation journey early" → re-engagement campaign).
- Is in canvas = users with
activeruns right now. - Is not in canvas = excludes only
activeruns (so includes both completed and exited users - useful when you want "anyone who's been through this journey at any point and isn't currently inside it").
Where to inspect what happened
- The Journey Runs drawer (Analytics in the journey editor topbar) lists each run with its status pill, version, current node, and timestamps. Expanding a row reveals the Run timeline (every node visited, with results) and Context variables (the run's resolved variable bag).
- For an exited run, the exit reason is surfaced in the API as
execution.exitReason. The Journey Runs UI shows the reason inline in the timeline once the run terminates.
Status transitions
draft ──▶ active ──▶ paused ──▶ active
│
└──▶ archived
- Only
draftjourneys can be mutated structurally (nodes / edges). activepauses when you hit Pause;pausedresumes when you reactivate.- Recurring journeys auto-pause when
endDatepasses ormaxOccurrencesis reached.
When you publish changes to an active journey, a new version is created. Existing users continue on their original version while new users get the updated one. See Version History for details.
Going further
- Journey Best Practices - guidelines for simple, well-timed, personalized journeys.
- Common Journey Patterns - ready-to-adapt flows: onboarding series, abandoned cart, win-back, and a WhatsApp support bot.
Legacy
The older "Settings" modal inside the node editor no longer holds re-entry policy, conversion tracking, tags, or anything else that's now on the wizard. It keeps only Personalized Variants (ML-driven variant selection for A/B/n journeys).