Build a workflow in the builder

A hands-on tutorial in Kustomer's visual workflow builder. Build a workflow that tags conversations from VIP customers, and see the JSON each builder action writes. Covers the workflows list, the canvas, triggers, the data picker, conditions, variables, testing, logs, saving and enabling, and what the builder can't do.

The workflow builder is a visual editor for the same JSON document you can read and write with the Workflows API. Every node you add and every field you fill writes a specific piece of that JSON. Once you can see the JSON behind each click, the builder stops being mysterious, and you can switch between the two whenever it's convenient.

In this tutorial you'll build one workflow from start to finish: when a conversation is created, look up its customer, and if the customer is a VIP, tag the conversation. After each builder action, you'll see the JSON it produces.

This page is about the builder. If you'd rather write the JSON yourself, follow Write a workflow in JSON, which writes a find-or-create workflow for an order web hook one key at a time and creates it through the API. For the ideas underneath both, read How workflows run and Data and variables.

Before you start

You need:

  • Access to Settings › Platform › Workflows in your Kustomer org.
  • A Boolean custom attribute on customers that marks VIPs. This tutorial calls it VIP, stored as custom.vipBool. Use your own field if you have one.
  • A tag for VIP conversations, and its ID. Workflows store tags as IDs, not names. If a conversation already has the tag, you can see its ID in that conversation's tags array, for example in a workflow's logs.
  • A test customer with the VIP field turned on, so you can check the result.

Find your way around

The workflows list

Settings › Platform › Workflows lists every workflow in your org.

PartWhat's there
FiltersUser Created, App Created, All Triggers, Scheduled Workflows, Callable Workflows
ColumnsName, Errors, Updated, Actions. Each workflow is either Activated or Deactivated
Row actionsDuplicate, Export JSON, View Error Logs
Template GalleryPrebuilt workflows. Choose Use This Workflow to start from one

Workflows installed by an app show "App workflows cannot be edited". You can still turn them on and off.

The canvas

The canvas draws the workflow as a tree, from the trigger at the top down to the last steps.

On the canvasWhat it isIn the JSON
Trigger node, badge 1The event that starts a run. Shows "Choose Trigger" until you pick onetrigger, always "id": "1"
Action node, numbered badgeA step that runs an action. Shows "Choose Action" in red until it has an action and no errorsAn entry in steps[] with an action
Condition nodeA condition on the path to the next step. Shows its name, or "Edit Condition" when it's unnamedA transition with a condition and meta.name
Plain lineA path that's always takenA step's only transition, { "op": "true", "values": [true] }
+ below a nodeAdds what comes next: Action, Condition Step, Condition Branch, or Paste after you've copied a step or conditionNew steps[] and transitions[] entries

Create the workflow and pick a trigger

  1. Create a custom workflow

    On the workflows list, select Add Workflow, then Custom Workflow ("Build your workflow from a clean slate"). In Create New Workflow, fill in:

    • Workflow Title: Tag VIP conversations
    • Workflow Description: Adds the VIP tag to new conversations from customers whose VIP field is true.

    Leave the Callable Workflow toggle off, and select Create. The title allows letters, numbers, spaces, -, _ and ..

    What Create writes
    {
      "name": "tag-vip-conversations",
      "description": "Adds the VIP tag to new conversations from customers whose VIP field is true.",
      "meta": { "displayName": "Tag VIP conversations" }
    }

    The title becomes two things: name, lowercased with spaces turned into -, and meta.displayName, exactly as you typed it. name must be unique in your org. New workflows start with enabled set to false, so nothing runs until you switch the workflow on.

  2. Choose the trigger

    Select the Choose Trigger node. In the side panel:

    • Type a Trigger Title, such as New conversation.
    • Under Trigger Information, set Trigger App to Kustomer and Trigger Event to Conversation Created. Details describes the event.
    trigger
    {
      "id": "1",
      "eventName": "kustomer.conversation.create",
      "meta": { "displayName": "New conversation" },
      "transitions": []
    }

    The trigger's output is step 1: the new conversation, flattened, so its ID is /#steps.1.id and its customer's ID is /#steps.1.customer. Triggers and actions lists every built-in trigger and its builder name.

Add a step and fill its params

  1. Add an action

    Select + below the trigger and choose Action. A new node appears with badge 2, connected by a plain line.

    trigger.transitions and steps
    {
      "trigger": {
        "id": "1",
        "eventName": "kustomer.conversation.create",
        "transitions": [{ "target": "B1gTq2Lxk", "condition": { "op": "true", "values": [true] } }]
      },
      "steps": [{ "id": "B1gTq2Lxk", "transitions": [] }]
    }

    The plain line is a transition whose condition always passes. The new step gets a generated ID; yours will be different from B1gTq2Lxk.

  2. Pick the action

    Select the new node. In the side panel, type an Action Title, Look up the customer. Under Action Information, set Action App to Kustomer and Action Event to Customer: Lookup (By ID).

    steps[0]
    {
      "id": "B1gTq2Lxk",
      "action": "kustomer.customer.find",
      "meta": { "displayName": "Look up the customer" },
      "params": {},
      "transitions": []
    }

    The conversation only holds the customer's ID. To read the customer's VIP field, you look the customer up first.

  3. Fill the identifier with the data picker

    Required fields now shows a count of 1: identifier. Select the field. A searchable picker opens, listing the earlier steps by number and name with their output fields. Under 1, choose Customer.

    steps[0].params
    { "identifier": "/#steps.1.customer" }

    The picker sets the whole field to one reference, /#steps.<stepId>.<path>. The field shows the step number and the field's name rather than the raw path.

How the data picker works

The picker is the easiest way to get paths right, because it builds them from each step's real output. It lists:

  • Earlier steps, by number and name, with their output fields. Choosing one writes /#steps.<stepId>.<path>. A nested field shows as a trail, such as Custom > VIP.
  • Variables. Choosing one writes /#variables.<variableId>. See Use workflow variables.
  • Functions, the /#fn: helpers, each with "Returns:" and "Example:". See the Expressions reference.
  • Value lists that depend on the field, such as Queues, Boolean Values (True and False), System Klasses and System Methods, and pickers for users, teams, statuses and priorities.

Optional inputs live under Optional fields, which shows how many you've used out of the total. Use Add field ("Search fields...") to add one or several, and Remove field to take one off. Removing a field deletes its key from params.

Mix text and values in code view

The picker can't combine text with a value. For that, turn on Toggle Code View (the </> icon) and type a template:

Text
Order {{{steps.1.custom.orderNumberStr}}} needs a VIP follow-up

What you type is saved when you press Enter or click away, and it's converted to the field's type first. The JSON can differ from what you typed:

Field typeYou typeSaved as
Arrayvip,priority["vip", "priority"] (split on commas)
Number1515
Booleantruetrue
Array/#steps.1.tags["/#steps.1.tags"] (see the warning below)
Any(empty)The param is removed

For a tags field, [] means "clear all tags". For a queue field, an empty value means "unassign".

A field is highlighted when what you typed doesn't match its type. Values that start with /#, and values that are a whole {{…}} template, are always accepted. Object fields, the ones that take JSON, open a JSON editor instead, and invalid JSON blocks saving with "Fix invalid JSON in action parameters before saving".

Add a condition

So far the workflow looks up every new conversation's customer. Now add the check: continue only if that customer is a VIP. In the builder, a condition sits on the path between two steps. In the JSON, it's the condition of a transition.

  1. Add a condition branch

    Select + below Look up the customer and choose Condition Branch. A condition node labelled "Edit Condition" appears. It's shown in an error style until it has a complete rule and a step to go to.

  2. Name it and set the rule

    Select the condition node. Type a name in the title field: Customer is VIP. Then, under Continue Only If:

    1. Select field: under 2, choose Custom > VIP.
    2. Operator: Equals. A Boolean field only offers Equals and Not equal.
    3. Select Value: true.
    steps[0].transitions[0] (no target yet)
    {
      "condition": { "op": "eq", "values": ["/#steps.B1gTq2Lxk.custom.vipBool", "true"] },
      "meta": { "name": "Customer is VIP" }
    }

    The field is on step 2's output, so the path uses step 2's generated ID, not the number 2. The builder saves values other than numbers as you typed them, so true is stored as the string "true". That works: eq casts the right side to the left side's type, so "true" becomes the boolean true. If a customer has no VIP value at all, the condition doesn't match.

  3. Add the tag step

    Select + below the condition and choose Action. Title it Add the VIP tag, and set Action App to Kustomer and Action Event to Conversation: Add Tags. Fill the two required fields:

    • id: from the picker, under 1, choose ID. That's the conversation's ID.
    • tags: turn on Toggle Code View and paste the VIP tag's ID.
    steps[0].transitions
    {
      "transitions": [
        {
          "target": "rkM8xV0Zp",
          "condition": { "op": "eq", "values": ["/#steps.B1gTq2Lxk.custom.vipBool", "true"] },
          "meta": { "name": "Customer is VIP" }
        }
      ]
    }
    steps[1]
    {
      "id": "rkM8xV0Zp",
      "action": "kustomer.conversation.append-tags",
      "meta": { "displayName": "Add the VIP tag" },
      "params": { "id": "/#steps.1.id", "tags": ["64f1c0ffee5ca1ab1e000002"] },
      "transitions": []
    }

    The condition now has a target. Add Tags adds the tag without touching the conversation's other tags. The new step has no transitions, so the run ends after it.

The condition editor

Every condition in the builder has the same parts: Select field, an operator, and Select Value. Use Toggle Code View to type a raw path or a template instead of picking a field. Try it here: build a condition on the tutorial's data and test it against three sample conversations.

The operator list depends on the field's type. Each label maps to one op in the JSON, and Conditions and variables explains exactly when each passes.

LabelopLabelop
ExistsexistsLess thanlt
Does not existdneLess than or equallte
EqualseqBeforelt-date
Not equalneqAftergt-date
Greater thangtContainscontains
Greater than or equalgteDoes not containnot-contains
Multiple Conditions (And)andMultiple Conditions (Or)or
Field typeOperators offered
TextExists, Does not exist, Equals, Not equal, Contains, Does not contain
Date (date-time text)Exists, Does not exist, Equals, Not equal, Before, After
NumberExists, Does not exist, Equals, Not equal, Greater than, Greater than or equal, Less than, Less than or equal
BooleanEquals, Not equal
ListExists, Does not exist, Equals, Not equal, Contains, Does not contain
Unknown, or a path typed in code viewAll twelve comparisons

The builder never offers the true and false operators. It writes true for you when it draws a plain line. Number values use a number input, so they're saved as numbers.

Combine rules with and and or

Select Add Condition to turn a single rule into a Multiple Conditions (And) group that holds the existing rule plus a new, empty one. Change the group to Multiple Conditions (Or) with its operator dropdown. Groups can hold other groups.

VIP and arrived by email
{
  "op": "and",
  "values": [
    { "op": "eq", "values": ["/#steps.B1gTq2Lxk.custom.vipBool", "true"] },
    { "op": "contains", "values": ["/#steps.1.channels", "email"] }
  ]
}

There's no "is one of" operator. To test "priority is 4 or 5", use an Or group of two Equals rules.

Branches and their order

A step can have several condition branches side by side, up to 10. Their left-to-right order is their priority: after the step runs, the branches are checked from left to right, and the first one that matches wins. Use the Move left and Move right arrows on a condition to reorder it. In the JSON, the order is the order of the step's transitions array.

If no branch matches, the run ends there. That's a normal, successful end, not an error. Put the most specific branch first, because a broad branch on the left swallows everything it matches.

Condition steps

Sometimes you want to branch without running an action first. Choose Condition Step from the + menu: it's a step with no action, and its condition branches do the routing. For example, in a workflow triggered by Message Created, a step that sends emails about refunds one way and emails about shipping another:

A Condition Step with two branches
{
  "id": "Hy3pQe7Lm",
  "meta": { "displayName": "Route by keyword" },
  "transitions": [
    {
      "target": "SkT0f9cQa",
      "meta": { "name": "Mentions refund" },
      "condition": { "op": "contains", "values": ["{{{toLower steps.1.subject}}}", "refund"] }
    },
    {
      "target": "rJ5vWn2Xb",
      "meta": { "name": "Mentions shipping" },
      "condition": { "op": "contains", "values": ["{{{toLower steps.1.subject}}}", "shipping"] }
    }
  ]
}

A subject that mentions both words only takes the left branch. To act on both, chain the checks instead: refund check, refund tag, shipping check, shipping tag. The patterns page has more shapes like this.

Save, test and enable

  1. Save

    Select Save and confirm: "Are you sure you wish to save this workflow? This will create a new version." Save sends the workflow's steps, trigger and response, and adds appVersion to each step, recording the app and version range of its action:

    A saved step
    {
      "id": "B1gTq2Lxk",
      "action": "kustomer.customer.find",
      "appVersion": "kustomer-^1.2.1",
      "meta": { "displayName": "Look up the customer" },
      "params": { "identifier": "/#steps.1.customer" },
      "transitions": [ … ]
    }

    Your version range may differ. Save is disabled, with a tooltip, when there's nothing to save ("No unsaved changes"), when an object field has invalid JSON, or for an app's workflow.

  2. Check the JSON

    Open the options menu and choose Edit workflow JSON. The editor shows name, description, trigger and steps exactly as Kustomer stores them. Compare it with the finished workflow below.

    The editor shows the last saved version, so save your canvas changes first. Update saves the JSON straight to the workflow, and Kustomer validates it. Malformed JSON shows "Invalid JSON.".

  3. Try a dry run

    If your options menu has Test workflow, use it before you switch the workflow on. It's a dry run in your browser: it doesn't run actions or change data.

    1. Edit the Sample Event Data, or select Regenerate from Workflow to build it from the fields the workflow uses.
    2. Select Run test. The Execution Flow shows how many steps ran, such as "3 steps executed", each step's Input data and Output data, and which branch was taken ("Next step") or skipped ("Transition not matched").
    3. Because actions don't run, edit the lookup step's output to set custom.vipBool to true, then choose Run from Here to check the VIP branch.
  4. Switch it on

    Turn on the switch in the header. It takes effect immediately, and it's separate from Save: it changes only enabled.

    What the switch sends
    { "enabled": true }

    You can't switch on a workflow while any condition lacks a target: "All conditions in active workflows must point to a target". Allow about a minute before the workflow starts matching new events.

  5. Watch a real run

    Open View Logs and turn Debug Mode on. Runs are recorded for the next hour, and the log shows the TOP 100 LOG EVENTS. Then create a conversation for your VIP test customer.

    Each entry, such as "Log Event 1", says when the run completed and how many seconds it took, and names its Last Step. Open it to see All Logged Steps For Event, with every step's input and output. A run whose last step is the lookup means the VIP condition didn't match: check custom.vipBool in the lookup's output. Use Debugging and logs for anything else.

Failed runs appear under View errors in the options menu, in Workflow Errors, as "Failed with Error at step …". Select Clear All Errors once you've fixed the cause. Temporary platform errors that Kustomer retries aren't listed there.

The rest of the header

ControlWhat it does
Undo and RedoEach names the change it will undo or redo
Version pickerLatest version, then earlier versions by date. An old version opens read-only. Kustomer keeps the 30 most recent versions
Edit infoOpens Edit Workflow Info, with Workflow Title and Workflow Description. It saves at once, and renaming the title also changes name, so look workflows up by ID in code
Workflow variablesOpens the variables modal. See below
View hook logsFor workflows triggered by a hook, what the hook received
Delete workflow"Deleting this workflow is immediate."

The finished workflow

This is what Edit workflow JSON shows after you save. Your step IDs, tag ID and appVersion ranges will differ.

tag-vip-conversations
{
  "name": "tag-vip-conversations",
  "description": "Adds the VIP tag to new conversations from customers whose VIP field is true.",
  "trigger": {
    "id": "1",
    "eventName": "kustomer.conversation.create",
    "meta": { "displayName": "New conversation" },
    "transitions": [
      { "target": "B1gTq2Lxk", "condition": { "op": "true", "values": [true] } }
    ]
  },
  "steps": [
    {
      "id": "B1gTq2Lxk",
      "action": "kustomer.customer.find",
      "appVersion": "kustomer-^1.2.1",
      "meta": { "displayName": "Look up the customer" },
      "params": { "identifier": "/#steps.1.customer" },
      "transitions": [
        {
          "target": "rkM8xV0Zp",
          "condition": { "op": "eq", "values": ["/#steps.B1gTq2Lxk.custom.vipBool", "true"] },
          "meta": { "name": "Customer is VIP" }
        }
      ]
    },
    {
      "id": "rkM8xV0Zp",
      "action": "kustomer.conversation.append-tags",
      "appVersion": "kustomer-^1.2.1",
      "meta": { "displayName": "Add the VIP tag" },
      "params": { "id": "/#steps.1.id", "tags": ["64f1c0ffee5ca1ab1e000002"] },
      "transitions": []
    }
  ]
}

When you write JSON by hand, you can choose readable step IDs, such as find-customer, instead of generated ones. Write a workflow in JSON shows how.

Use workflow variables

Variables hold values you don't want written into the workflow itself, such as API keys and signing secrets. Each variable belongs to one workflow.

  1. Open the options menu and choose Workflow variables ("Create variables to be used within this workflow."). Names can't contain spaces.
  2. Fill in Name and Value and select Save on that row. Each row saves on its own, separately from the workflow's Save button.
  3. Saved values are masked: you see XXXXXXXXXXXXXXXXXXXXXXXX followed by the last 6 characters. To rename a variable, select Edit and leave the value blank. Deleting a variable that's in use stops the workflow from working properly.

To use a variable in a field, choose it from the picker's Variables section. In object fields, the Variables button copies a ready-made template instead. Both reference the variable by its ID, not its name:

Variables in params
{
  "uri": "/#variables.6531c0ffee5ca1ab1e0000a1",
  "headers": { "Authorization": "Bearer {{{variables.6531c0ffee5ca1ab1e0000a1}}}" }
}

The ID is the variable's 24-character ID. Because variables belong to one workflow, copying a workflow's JSON to another workflow or org breaks every variable reference until you create the variables there and update the IDs. Data and variables covers variables in depth.

Other kinds of trigger

Hooks

Set Trigger App to Hooks to start a workflow from an inbound webhook, an email hook or a form hook. Add New Hook opens Add Kustomer Hook, with Title of Hook and Select Type: Email Hook, Web Hook or Form Hook. Hook Information then shows the Hook Address that starts the hook, the Hook Key, and Example Webhook Data.

Paste a sample payload into Example Webhook Data. The builder reads field types from it and lists the fields in the data picker, so you can pick them instead of typing /#steps.1.attributes.data.<field>. The sample is only saved in your browser ("This data will only be saved locally").

Callable workflows

Turn on Callable Workflow in Create New Workflow to make a workflow that other workflows run, usually later, with the Scheduler app. The toggle only appears when the Scheduler app is installed. The builder starts the trigger as:

JSON
{ "id": "1", "callable": true, "schema": {}, "transitions": [] }

and opens Edit Input Parameters ("Data fields expected by this workflow"). On the Input Parameters tab, select Add Parameter for each input and give it a name, a type (String, Number or Array) and, if needed, Required Field. The Output Parameters tab declares what the workflow sends back. Update writes trigger.schema and response.schema as { "properties": { "<name>": { "type": "…" } }, "required": [ … ] } in your draft. Save the workflow to keep them.

In a callable workflow, step 1 is just the inputs, so read them as /#steps.1.<input>. To run it, add the Scheduler app's action to another workflow: pick the callable workflow, fill its inputs, and choose Select a Time Type, then Set an Exact Date and Time or Set a Relative Time. See Callable and scheduled workflows.

What the builder can't do

Added in Edit workflow JSON: stop quietly if the tag can't be added
{
  "id": "rkM8xV0Zp",
  "action": "kustomer.conversation.append-tags",
  "params": { "id": "/#steps.1.id", "tags": ["64f1c0ffee5ca1ab1e000002"] },
  "errorCases": [
    {
      "condition": { "op": "and", "values": [{ "op": "eq", "values": ["/#error.status", 400] }] },
      "errorAction": { "type": "stop-processing" }
    }
  ],
  "transitions": []
}

The error is at /#error, never at /#steps.<id>.error. Error cases don't apply to a 5xx from a Kustomer action: Kustomer retries the run from step 1, so earlier steps run again. See Handle errors.

The builder also follows the rules of the workflow JSON:

  • No loops. A transition can't go back to a step that's already on its path. The canvas warns "⚠️ Cycle detected: … already visited", and saving is rejected. To repeat work later, schedule a callable workflow.
  • One end per branch. Give each branch its own final step, even if two branches end with the same action.
  • Unique branches. Two identical transitions on one step are rejected.

Export JSON, Duplicate and Import

Export JSON and Duplicate copy name, description, meta, steps and trigger, and nothing else:

  • The exported name is export-<name>--<uuid>, with the display name prefixed "Export". A duplicate's name starts with copy-, with the display name prefixed "Copy".
  • enabled isn't copied, so copies start switched off. Neither is logging.
  • A callable workflow's output parameters (response) are dropped. Add them again with Edit Input Parameters.
  • Variables aren't copied, and their IDs belong to the original workflow. Create the variables on the copy and update every reference.

To load a file, select Add Workflow, then Import Workflow ("Import workflow with a JSON file"), choose a .json file and select Import. The file is created as is, so its name must be valid and unique in the org. When you copy a workflow to another org, also replace org-specific IDs such as tags, teams and users; see Workflow JSON.

Deleting steps

Deleting a step opens Delete Step ("What would you like to delete?") with two choices: Delete this step only, which reconnects the steps below it to its parent, or Delete this step and all steps below. A step with several branches has to be deleted with everything below it. A condition can't be deleted while steps hang off it.

Builder vocabulary ↔ JSON

In the builderIn the JSON
Workflow Titlename (lowercased, spaces become -) and meta.displayName (as typed)
Workflow Descriptiondescription, up to 4,096 characters
The on/off switchenabled. New workflows start false
Debug Mode in View Logslogging: true, which records runs for one hour
Trigger Titletrigger.meta.displayName
Trigger App and Trigger Eventtrigger.eventName, such as kustomer.conversation.create
Callable Workflow toggletrigger: { "id": "1", "callable": true, "schema": { … }, "transitions": [ … ] }. Kustomer sets eventName to kustomer.workflow.<workflowId>.call
Input Parameters tabtrigger.schema.properties.<name>.type and trigger.schema.required
Output Parameters tabresponse.schema.properties and response.schema.required
Badge 1The trigger, id: "1". Its output is /#steps.1…
Action nodeA steps[] entry: id, action, params, transitions, appVersion, meta.displayName
Action TitleThe step's meta.displayName
Action App and Action Eventaction, such as kustomer.conversation.append-tags, plus appVersion, such as kustomer-^1.2.1
Required and optional fieldsparams.<name>. Removing an optional field deletes the key
Picking a step's field"/#steps.<stepId>.<path>", as the whole value
Picking a variable"/#variables.<variableId>"
Picking a function"/#fn:<name>,<path>,…"
Code view text with {{…}}A Handlebars template string
A conditionA transition: { "target", "condition": { "op", "values": [left, right] }, "meta": { "name" } }
Multiple Conditions (And) or (Or)"condition": { "op": "and" | "or", "values": [ { "op", "values" }, … ] }
Condition BranchAnother entry in the same step's transitions, in left-to-right order
Condition StepA step with no action, whose transitions do the branching
A plain line between two nodesThe step's only transition, { "op": "true", "values": [true] }
Delete this step onlyThe parent's transition is re-pointed to the deleted step's child

Next