Workflows

How Kustomer workflows run. A trigger event starts a run, steps call actions with params built from earlier outputs, and transitions with conditions choose the path. Covers the anatomy, run lifecycle, limits and API.

A workflow is a small program that runs inside Kustomer whenever an event happens. Admins build workflows in Settings › Platform › Workflows without code. Underneath, each workflow is a JSON document you can read, write and version through the Workflows API.

Use workflows for logic that should run next to your Kustomer data: tagging and routing conversations, keeping records in sync, calling your own services when something changes, and handling data from inbound webhooks.

New to workflows? Read these in order. Each one has an interactive scene you can step through.

The rest of this section is reference material.

InteractiveA workflow run, step by step
sync-closed-conversationsevent: status open → done
Trigger · 1
kustomer.conversation.update
Action · lookup
kustomer.customer.find
Action · post
kustomer.rest-api.json
Action · tag
kustomer.conversation.append-tags
status < 300
Action · note
kustomer.note.create
status ≥ 300
Transitions
    Context
    {
      "steps": {}
    }
    Each step's output is stored under its ID in the context, so later params reference it as /#steps.<id>.<path>. After a step runs, its transitions are checked in order and the first match wins.

    Anatomy

    Every workflow has the same five building blocks.

    PartWhat it isJSON
    TriggerThe event that starts a run, such as kustomer.conversation.update. Its output, usually the record that changed plus a changes diff, is step 1trigger.eventName
    StepsActions to run, such as finding a customer, updating a conversation or calling a URLsteps[].action
    ParamsEach step's inputs. Literal values, references to earlier outputs, or templatessteps[].params
    TransitionsWhere to go after a step, each guarded by a conditiontransitions[].condition, .target
    Error casesWhat to do when a step fails: continue or stopsteps[].errorCases

    Here's the workflow from the diagram above as JSON:

    sync-closed-conversations.json
    {
      "name": "sync-closed-conversations",
      "description": "Post closed conversations to our analytics service",
      "enabled": true,
      "trigger": {
        "id": "1",
        "eventName": "kustomer.conversation.update",
        "transitions": [
          {
            "target": "lookup",
            "condition": { "op": "eq", "values": ["/#steps.1.changes.attributes.status.after", "done"] }
          }
        ]
      },
      "steps": [
        {
          "id": "lookup",
          "action": "kustomer.customer.find",
          "params": { "identifier": "/#steps.1.customer" },
          "transitions": [{ "target": "post", "condition": { "op": "true", "values": [true] } }]
        },
        {
          "id": "post",
          "action": "kustomer.rest-api.json",
          "params": {
            "method": "post",
            "uri": "https://api.example.com/kustomer/closed",
            "headers": { "Content-Type": "application/json", "Authorization": "Bearer {{{variables.6513b2f4a4b1e1001a6b3f20}}}" },
            "json": true,
            "data": {
              "conversationId": "/#steps.1.id",
              "email": "/#steps.lookup.emails.0.email",
              "closedAt": "/#steps.1.lastDone.createdAt"
            }
          },
          "transitions": [
            { "target": "tag", "condition": { "op": "lt", "values": ["/#steps.post.response.statusCode", 300] } },
            { "target": "note", "condition": { "op": "gte", "values": ["/#steps.post.response.statusCode", 300] } }
          ]
        },
        {
          "id": "tag",
          "action": "kustomer.conversation.append-tags",
          "params": { "id": "/#steps.1.id", "tags": ["64f1c0ffee5ca1ab1e000001"] },
          "transitions": []
        },
        {
          "id": "note",
          "action": "kustomer.note.create",
          "params": { "conversation": "/#steps.1.id", "body": "Sync failed: HTTP {{steps.post.response.statusCode}}" },
          "transitions": []
        }
      ]
    }

    The Authorization header reads a workflow variable. 6513b2f4a4b1e1001a6b3f20 is the variable's ID, not its name: workflows always refer to variables by ID.

    Three rules explain most of what you see:

    1. Outputs are keyed by step ID. When a step finishes, its output is stored at steps.<id> in the run's context. The trigger is always step 1. Steps you create in the builder get short random IDs such as SJsVbyiGf.
    2. References keep their type; templates make strings. A param that is exactly "/#steps.1.id" resolves to the value itself, whether that's a string, number, array or object. Any other string is rendered as a Handlebars template, such as "Sync failed: HTTP {{steps.post.response.statusCode}}", and the result is always a string. See Conditions and variables.
    3. The first matching transition wins. After a step runs, its transitions are checked in order. The first one whose condition passes picks the next step. If none match, the run ends. That's normal, and it's how most runs end: the trigger fires on every update, and the first condition filters out the ones you don't care about.

    Runs only move forward. A transition can't point back to a step that's already on the path to it, and Kustomer rejects such a workflow when you save it. To repeat work later, schedule a callable workflow. For the full engine model, see How workflows run.

    The life of a run

    ActionsWorkflow engineKustomerActionsWorkflow engineKustomerEach matching workflow runs in turnloop[Until no transition matches]Event (kustomer.conversation.update)1Find enabled workflows with this trigger2Normalize event into step 1 output3Check transitions in order4Run next step with resolved params5Output stored at steps.<id>6Run complete (or error recorded)7
    Diagram source
    sequenceDiagram
      autonumber
      participant K as Kustomer
      participant W as Workflow engine
      participant A as Actions
      K->>W: Event (kustomer.conversation.update)
      W->>W: Find enabled workflows with this trigger
      Note over W: Each matching workflow runs in turn
      W->>W: Normalize event into step 1 output
      loop Until no transition matches
        W->>W: Check transitions in order
        W->>A: Run next step with resolved params
        A-->>W: Output stored at steps.<id>
      end
      W-->>K: Run complete (or error recorded)
    Runtime behavior
    Matching delay~60 seconds
    Run timeout5 minutes
    Branches10 per step
    REST timeout60 seconds
    Rate limitsskipped
    Orderingnot guaranteed
    System retriesfrom step 1
    Versions30 kept

    Where workflow events come from

    Trigger familyEvent namesLearn more
    Records created or updatedkustomer.customer.create, kustomer.conversation.update, kustomer.kobject.<klass>.create and othersTriggers and actions
    Inbound webhookskustomer.hooks.web.<id>.receive and the form and email equivalentsInbound webhooks
    Other workflowskustomer.workflow.<id>.callCallable and scheduled workflows
    AppsEvent names an app defines, such as kustomer.app.<app>.<event>Apps: workflows, actions and triggers

    Manage workflows with the API

    Workflows are ordinary resources. Read them to back them up, diff them in code review, or copy them between orgs.

    OperationEndpoint
    List workflows (filter by enabled, eventName, callable)GET /v1/workflows
    Create a workflowPOST /v1/workflows
    Get a workflowGET /v1/workflows/{id}
    Update a workflow (only the root keys you send)PUT /v1/workflows/{id}
    List, create, update and delete variables/v1/workflows/{id}/variables
    List failed runsGET /v1/workflows/{id}/processes
    curl "https://acme.api.kustomerapp.com/v1/workflows?eventName=kustomer.conversation.update&enabled=true" \
      -H "Authorization: Bearer $KUSTOMER_API_KEY"

    Next