Skip to main content

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.

Naming

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.

#TabWhat you configure
1DetailsName, description, status, tags.
2DeliverySend type and trigger configuration.
3AudienceMode chooser → filter builder → re-entry policy → exit criteria.
4ConversionPrimary + secondary conversion events.
5Journey editorThe full-screen node editor.
6SummaryRead-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 as draft and flip to active on 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 typeWhen users enter
immediateOnce, right after publish, for every user matching the audience.
scheduledOnce at a chosen date/time, for every user matching at that moment.
recurringOn a schedule (daily / weekly / monthly / custom cron). Audience re-evaluated each firing.
triggerWhen 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).

  • 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:

PolicyBehaviorminIntervalMinutes 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.
restartExit the old execution, start a new one.(field hidden - restart already kills the old execution)
parallelAllow 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:

StatusMeaning
activeThe user is currently moving through the journey.
completedThe user reached an Exit node naturally - the happy path finished. Set when the runtime hits an Exit step.
exitedThe 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.
failedA 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.
No flow-level timeout

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 active runs right now.
  • Is not in canvas = excludes only active runs (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 draft journeys can be mutated structurally (nodes / edges).
  • active pauses when you hit Pause; paused resumes when you reactivate.
  • Recurring journeys auto-pause when endDate passes or maxOccurrences is reached.
Version control

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

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).

Next steps