Triggers and actions
Every built-in workflow trigger and action, the shape of trigger output, how Kustomer actions report not-found and errors, and how to build callable and scheduled workflows.
A trigger names the event that starts a run. An action is a step that does something: finds or changes a record, calls a URL, or answers a caller. The built-in triggers and actions come from the Kustomer app, which every org has. Installed apps add their own. Use their names exactly as shown here in workflow JSON.
Built-in triggers
Two more families start workflows: inbound webhooks (kustomer.hooks.<type>.<id>.receive) and callable workflows (kustomer.workflow.<id>.call). Installed apps define their own, such as kustomer.app.<app>.<event>.
What the trigger outputs
A record trigger's output, step 1, is the record flattened: attributes sit at the top level, relationships become plain IDs, and the update diff is under changes.
{
"id": "6512f0c2a4b1e1001a6b2c90",
"name": "Order 4417 hasn't arrived",
"status": "done",
"priority": 3,
"channels": ["email"],
"tags": ["64f1c0ffee5ca1ab1e000001"],
"assignedUsers": ["5f8481c6a22ab5b9d5f2dd99"],
"assignedTeams": [],
"messageCount": 4,
"custom": { "orderNumberStr": "4417" },
"lastDone": { "createdAt": "2026-10-21T15:20:42.554Z", "createdBy": "5f8481c6a22ab5b9d5f2dd99" },
"customer": "6512ef18a4b1e1001a6b2c11",
"brand": "5f8481c6a22ab5b9d5f2dd8b",
"changes": {
"attributes": {
"status": { "op": "replace", "before": "open", "after": "done" }
}
}
}So /#steps.1.status is the current status, /#steps.1.customer is the customer's ID (look the customer up if you need more), and /#steps.1.changes.attributes.status.after is set only when this update changed the status. Create triggers have no changes.
Other trigger families shape step 1 differently:
- Inbound web hooks put the posted body under
attributes.data, so a field namedemailis/#steps.1.attributes.data.email. - Callable workflows get only the caller's inputs, cast to the trigger's
schema. Read an input namedconversationIdas/#steps.1.conversationId, never/#steps.1.inputs.conversationId.
Built-in actions
The tables list each action's required inputs. Most actions accept many more optional ones, matching the fields of the record they write; the builder shows them all.
Use append-tags and remove-tags rather than update with tags: update replaces the whole array, so a concurrent change can be lost.
message.create records a message on a conversation. It doesn't send anything to the customer. To reply on a channel, see Conversations and messages.
How Kustomer actions succeed and fail
Kustomer actions call the same APIs you do, and the step's outcome follows the response:
Always supply required inputs. The request is sent even when a required input is missing, so a step with a missing or empty required input may fail or return empty output. 0, false and "" count as missing for this check. A param whose reference resolves to null, undefined or an empty object isn't sent at all. The one exception is queue, where null is kept and means "unassign".
Because a 5xx reruns the whole workflow, design steps before a risky one to be safe to repeat: prefer lookups and Add Tags over creating records, or look for the record before you create it.
Because not-found isn't a failure, branch on the result after every lookup. This is the pattern Kustomer's own apps use:
{
"id": "find",
"action": "kustomer.customer.find-by-email",
"params": { "email": "/#steps.1.attributes.data.email" },
"transitions": [
{ "target": "update", "condition": { "op": "exists", "values": ["/#steps.find.id", ""] }, "meta": { "name": "Customer exists" } },
{ "target": "create", "condition": { "op": "dne", "values": ["/#steps.find.id", ""] }, "meta": { "name": "New customer" } }
]
}The REST actions behave differently: they never fail on a status code. See Call your own code.
Utility actions
REST API
kustomer.rest-api.json sends data as JSON and, with "json": true, parses the response body into an object. kustomer.rest-api.generic sends data as a string, for example form-encoded or XML, and returns the body as a string. Both output { response: { statusCode, headers }, body }. The full guide, including signing, secrets and URL restrictions, is in Call your own code.
Regex match
Extracts values from text such as an email subject or message body.
{
"id": "order-number",
"action": "kustomer.regex-match.generic",
"params": { "testString": "/#steps.1.preview", "regex": "order\\s*#?(\\d{4,})", "flags": "i" },
"transitions": [{ "target": "lookup-order", "condition": { "op": "exists", "values": ["/#steps.order-number.match"] } }]
}{ "match": "order #4417", "captures": ["4417"], "index": 7, "original": "Hi, my order #4417 hasn't arrived" }Reference the first capture group as /#steps.order-number.captures.0. Without a match, the output has only an empty captures, so check exists on match before using it.
The step runs JavaScript's RegExp and String.match():
- Pass flags in the
flagsparam. Inline flags such as(?i)are a syntax error. An invalid pattern fails the step with the engine's error, such asInvalid regular expression: /(?i)refund/: Invalid group. - Leave out
gto get capture groups. Withg,matchis the first full match,capturesholds the other full matches, andindexandoriginalare missing. - Escape backslashes in JSON:
\dis written"\\d". - If
regexortestStringresolves to nothing, the step fails withMissing param regexorMissing param testString.
{ "id": "order-number", "action": "kustomer.regex-match.generic", "params": { "testString": "/#steps.1.preview", "regex": "order\\s*#?(\\d{4,})", "flags": "i" }, "transitions": [ { "target": "next-step", "condition": { "op": "exists", "values": [ "/#steps.order-number.match" ] } } ] }
regex: "order\\s*#?(\\d{4,})"{ "match": "order #4417", "captures": [ "4417" ], "index": 7, "original": "Hi, my order #4417 hasn't arrived" }
/#steps.order-number.match"order #4417"/#steps.order-number.captures.0"4417"/#steps.order-number.index7
RegExp syntax the workflow step uses. Type the pattern as you’d write it in code; the step JSON shows the escaping it needs.Callable and scheduled workflows
A callable workflow has no event trigger. Instead, its trigger declares the inputs it accepts, and something else calls it, most often the Scheduler app's Schedule a Workflow action.
{
"name": "send-followup",
"enabled": true,
"trigger": {
"id": "1",
"callable": true,
"schema": {
"type": "object",
"properties": { "conversationId": { "type": "string" }, "reason": { "type": "string" } },
"required": ["conversationId"]
},
"transitions": [{ "target": "load", "condition": { "op": "exists", "values": ["/#steps.1.conversationId"] } }]
},
"response": {
"schema": { "properties": { "noteId": { "type": "string" } }, "required": [] }
},
"steps": [
{
"id": "load",
"action": "kustomer.conversation.find-by-id",
"params": { "id": "/#steps.1.conversationId" },
"transitions": [{ "target": "note", "condition": { "op": "neq", "values": ["/#steps.load.status", "done"] } }]
},
{
"id": "note",
"action": "kustomer.note.create",
"params": { "conversation": "/#steps.load.id", "body": "Follow up: {{steps.1.reason}}" },
"transitions": [{ "target": "respond", "condition": { "op": "true", "values": [true] } }]
},
{
"id": "respond",
"action": "kustomer.workflow.respond",
"params": { "outputs": { "noteId": "/#steps.note.id" } },
"transitions": []
}
]
}- Don't set
eventName. Kustomer sets it tokustomer.workflow.<workflowId>.callwhen you save. - A callable trigger needs
id,callable,schemaandtransitions. In the builder, the Input Parameters tab of Edit Input Parameters writestrigger.schema. - The caller's inputs are cast to
schemaand become step1's output, and nothing else is added, soconversationIdis/#steps.1.conversationId. - The root
response.schemadeclares the outputs the workflow sends back. It's the builder's Output Parameters tab. kustomer.workflow.respondreturnsoutputsto a caller that waits for a result, such as a Kustomer AI agent that calls the workflow as a tool. Add it at the end of each branch that should answer. The caller gets theoutputson success, "no response" if the run ends without reaching a respond step, or an error result if the run fails.- Calls to callable workflows aren't rate limited.
- There's no public REST endpoint that calls a workflow directly. To run one from your own code, post to an inbound web hook and use a workflow triggered by it.
Schedule a workflow
The Scheduler app's action runs a callable workflow later. Re-check the state in the callable workflow, as load does above, because it might have changed by the time it runs.
{
"id": "schedule",
"action": "kustomer.app.scheduler.schedule-workflow",
"appVersion": "scheduler-^1.0.1",
"params": {
"workflowId": "6513a0d1a4b1e1001a6b3e01",
"runAt": { "type": "relative", "value": 86400 },
"inputs": { "conversationId": "/#steps.1.id", "reason": "No reply after 24 hours" }
},
"transitions": []
}workflowId, runAt and inputs are all required; pass {} when there are no inputs. A relative value must be a whole number of seconds. When the time comes, the callable workflow runs with those inputs as its step 1 output. To keep a scheduled reminder within working hours, compute an absolute time first, or check /#fn:inBusinessHours in the callable workflow. See the Expressions reference.
The step fails with one of these messages when its inputs are wrong:
Actions and triggers from apps
Apps add triggers and actions with their own names, such as kustomer.app.<app>.<event>. A step that uses an app's action includes appVersion, such as "scheduler-^1.0.1", so the workflow keeps working as the app updates within that major version. See Apps: workflows, actions and triggers.