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

Source: https://developer.kustomer.com/docs/workflows

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](#manage-workflows-with-the-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](https://developer.kustomer.com/docs/events/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.

_A workflow called sync-closed-conversations with five nodes: a conversation.update trigger, a customer.find action, a rest-api.json action, and two branches, append-tags when the status code is below 300 and note.create when it is 300 or above. Three scenarios play through it. In the closed scenario the trigger condition on changes.attributes.status.after equals done matches, the REST call returns 200 and the conversation is tagged. In the service down scenario the REST call returns 503, which does not fail the step, and the workflow branches to create a note. In the priority change scenario the trigger condition fails and the run ends after the trigger. A context panel shows each step's output stored under its ID._

## Anatomy

Every workflow has the same five building blocks.

| Part            | What it is                                                                                                                                          | JSON                                 |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| **Trigger**     | The event that starts a run, such as `kustomer.conversation.update`. Its output, usually the record that changed plus a `changes` diff, is step `1` | `trigger.eventName`                  |
| **Steps**       | Actions to run, such as finding a customer, updating a conversation or calling a URL                                                                | `steps[].action`                     |
| **Params**      | Each step's inputs. Literal values, references to earlier outputs, or templates                                                                     | `steps[].params`                     |
| **Transitions** | Where to go after a step, each guarded by a condition                                                                                               | `transitions[].condition`, `.target` |
| **Error cases** | What to do when a step fails: continue or stop                                                                                                      | `steps[].errorCases`                 |

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

```json title="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](https://developer.kustomer.com/docs/workflows/conditions-and-expressions#variables-and-secrets). `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](https://developer.kustomer.com/docs/workflows/conditions-and-expressions).
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](https://developer.kustomer.com/docs/workflows/triggers-and-actions#callable-and-scheduled-workflows). For the full engine model, see [How workflows run](https://developer.kustomer.com/docs/workflows/how-workflows-run).

## The life of a run

```mermaid
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**

> **Avoid loops:** A workflow that updates the record that triggered it fires the trigger again. Guard every update trigger with a condition on `changes` (what changed in _this_ update), not on the current state. For example, check `/#steps.1.changes.attributes.status.after` rather than `/#steps.1.status`.

## Where workflow events come from

| Trigger family             | Event names                                                                                              | Learn more                                                                                                                              |
| -------------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Records created or updated | `kustomer.customer.create`, `kustomer.conversation.update`, `kustomer.kobject.<klass>.create` and others | [Triggers and actions](https://developer.kustomer.com/docs/workflows/triggers-and-actions#built-in-triggers)                            |
| Inbound webhooks           | `kustomer.hooks.web.<id>.receive` and the form and email equivalents                                     | [Inbound webhooks](https://developer.kustomer.com/docs/events/inbound-webhooks)                                                         |
| Other workflows            | `kustomer.workflow.<id>.call`                                                                            | [Callable and scheduled workflows](https://developer.kustomer.com/docs/workflows/triggers-and-actions#callable-and-scheduled-workflows) |
| Apps                       | Event names an app defines, such as `kustomer.app.<app>.<event>`                                         | [Apps: workflows, actions and triggers](https://developer.kustomer.com/docs/apps/workflows-actions-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.

| Operation                                                     | Endpoint                                                                                                            |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| List workflows (filter by `enabled`, `eventName`, `callable`) | [`GET /v1/workflows`](https://developer.kustomer.com/api/workflows/get-workflows)                                   |
| Create a workflow                                             | [`POST /v1/workflows`](https://developer.kustomer.com/api/workflows/create-a-workflow)                              |
| Get a workflow                                                | [`GET /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/get-a-workflow-based-on-the-id)             |
| Update a workflow (only the root keys you send)               | [`PUT /v1/workflows/{id}`](https://developer.kustomer.com/api/workflows/update-a-workflow-based-on-the-id)          |
| List, create, update and delete variables                     | [`/v1/workflows/{id}/variables`](https://developer.kustomer.com/api/workflows/get-all-variables-for-a-workflow)     |
| List failed runs                                              | [`GET /v1/workflows/{id}/processes`](https://developer.kustomer.com/api/workflows/get-processes-errors-by-workflow) |

```bash title="cURL"
curl "https://acme.api.kustomerapp.com/v1/workflows?eventName=kustomer.conversation.update&enabled=true" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY"
```

> **For agents editing workflows:** `PUT /v1/workflows/{id}` updates only the root keys you send, and each one replaces the stored value wholesale. Fetch the workflow, change the JSON, and send `trigger` and `steps` together, because steps and transitions depend on each other. Keep step IDs stable, because every `/#steps.<id>` reference depends on them, and refer to workflow variables by their ID. The full schema is in [Workflow JSON](https://developer.kustomer.com/docs/workflows/workflow-json), and a worked example is in [Write workflow JSON](https://developer.kustomer.com/docs/workflows/write-workflow-json).

## Next
