Workflows, actions and triggers
Bring data into Kustomer with app hooks, name it with triggers, call your API with REST actions, ship ready-made workflows, and subscribe to events with outbound webhooks.
Most integrations follow one loop: your platform tells Kustomer something happened, a workflow decides what to do, and Kustomer calls your platform back. An app packages each piece so it's installed and configured the same way in every org.
Diagram source
flowchart LR P[Your platform] -- POST --> H[hooks<br/>per-install URL] H -- event --> T[triggers<br/>name + output shape] T --> W[workflows] W --> K[Kustomer actions<br/>create, update, find] W --> A[actions<br/>your REST API] A -- HTTPS --> P E[(Kustomer events)] -- outboundWebhooks --> P
This page covers what's specific to apps. For how workflows run, their JSON format, conditions and expressions, see the Workflows section.
Hooks: get data in
{
"hooks": [
{
"eventName": "kustomer.app.acme_orders.order-updated",
"displayEventName": "Acme order updated",
"description": "Acme posts the full order whenever it changes.",
"type": "web"
}
]
}eventNamestringrequired- The event the hook emits. It must start with
kustomer.app.to reach workflows; usekustomer.app.<app>.<event>. Letters, digits,.,-and_, up to 128 characters. displayEventNamestring- The name admins see in the hooks list and the workflow builder.
descriptionstring- What the data is and when it arrives.
typestringrequiredweb(needs an API key with theorg.hooksrole),form(public URL, no auth), oremail(an email address that parses JSON or JSON-LD from messages).
Each install gets its own hook URL, which admins find on the app's page in the App Directory. Your platform needs that URL, so either ask admins to paste it into your platform (say how in postInstallMessage), or have a custom settings page read it and register it with your platform for them.
For request formats, size limits and debugging, see Inbound webhooks.
Triggers: name the event
A trigger tells the workflow builder about a hook's event: what to call it, and what data a workflow receives.
{
"triggers": [
{
"eventName": "kustomer.app.acme_orders.order-updated",
"description": "An order changed in Acme",
"outputTemplate": {
"orderId": "/#data.attributes.data.id",
"email": "/#data.attributes.data.customer.email",
"status": "/#data.attributes.data.status",
"total": "/#data.attributes.data.total"
},
"outputSchema": {
"type": "object",
"properties": {
"orderId": { "type": "string" },
"email": { "type": "string" },
"status": { "type": "string" },
"total": { "type": "number" }
},
"additionalProperties": false
},
"sampleOutputContext": { "orderId": "4417", "email": "ada@example.com", "status": "shipped", "total": 86.5 }
}
]
}eventNamestringrequired- The hook's
eventName. descriptionstring- What the event means.
outputTemplateobject- Maps the event into the trigger's output with
/#references. A hook event's posted body is atdata.attributes.data. outputSchemaJSON Schema- Casts each output value to its type and drops anything
additionalProperties: falsedoesn't allow. sampleOutputContextobject- Example output, so the builder can offer fields to pick from before any data arrives.
With both outputTemplate and outputSchema, a workflow reads the shaped values as /#steps.1.orderId. Without them, step 1 is the raw hook record and the posted body is at /#steps.1.attributes.data.<field>. The shaped form is easier for admins building their own workflows, and casting means "86.50" arrives as the number 86.5.
Name the trigger in the builder with i18n keys.
Actions: call your API
An action is a reusable workflow step with typed inputs. Third-party apps use the rest_api type, which makes an HTTP request:
{
"actions": [
{
"name": "kustomer.app.acme_orders.refund",
"description": "Refund an Acme order",
"type": "rest_api",
"appSettings": {
"apiKey": { "key": "acme_orders.default.apiKey" },
"storeDomain": { "key": "acme_orders.default.storeDomain" }
},
"inputTemplate": {
"uri": "https://{{storeDomain}}/api/orders/{{orderId}}/refunds",
"method": "POST",
"headers": { "Authorization": "Bearer {{apiKey}}", "Content-Type": "application/json" },
"data": { "amount": "/#amount", "reason": "/#reason" },
"json": true
},
"inputSchema": {
"type": "object",
"properties": {
"orderId": { "type": "string" },
"amount": { "type": "number" },
"reason": { "type": "string" }
},
"required": ["orderId"],
"additionalProperties": false
},
"outputTemplate": { "response": "/#response", "body": "/#body" },
"outputSchema": {
"type": "object",
"properties": { "response": { "type": "object" }, "body": { "type": "object" } },
"additionalProperties": false
},
"sampleOutputContext": { "response": { "statusCode": 201 }, "body": { "id": "rf_19", "status": "pending" } }
}
]
}namestringrequired- Globally unique:
kustomer.app.<app>.<action>. Letters, digits,.,-and_, up to 128 characters. Kustomer applies the prefix if you leave it off. typestringrequiredrest_api. Other types are reserved for Kustomer's own apps.descriptionstring- Up to 4,096 characters.
appSettingsobject- Settings exposed as Handlebars variables in
inputTemplate:{ "<variable>": { "key": "<app>.<category>.<name>" } }. Secret settings work here and are never shown to admins. appAccountNamestringdefaultto send the app's OAuth token. See OAuth for apps.inputTemplateobject- The request:
uri,method(get,post,put,patchordelete),headers,qs,data(a JSON body object) orbody(a string), andjson. inputSchemaJSON Schema- The step's inputs. Values are cast to their types; missing
requiredproperties or disallowed extras reject the step. outputTemplateobject- Shapes the step's output from
/#response(status code and headers) and/#body. outputSchemaJSON Schema- Casts and filters the output. Applied after
outputTemplate. sampleOutputContextobject- Example output for the builder.
Inside inputTemplate, {{name}} in strings inserts an input or setting, and a whole-value "/#amount" inserts the input with its type intact. Set json: true to parse the response body as JSON; otherwise body is a string.
A workflow step that uses the action names it and the app version range:
{
"id": "refund",
"action": "kustomer.app.acme_orders.refund",
"appVersion": "acme_orders-^1.4.0",
"params": { "orderId": "/#steps.1.orderId", "amount": "/#steps.1.total", "reason": "Requested in chat" },
"transitions": []
}Give the action, and each input, a display name with i18n. Without one, admins see the raw action name.
Workflows: wire it together
App workflows use the same workflow JSON as any other, with two differences: names must be unique across every workflow in the org, and steps that use app actions carry appVersion.
{
"workflows": [
{
"name": "acme-orders-sync",
"description": "Create or update an Acme order KObject when Acme sends an update.",
"trigger": {
"eventName": "kustomer.app.acme_orders.order-updated",
"appVersion": "acme_orders-^1.4.0",
"transitions": [{ "target": "findCustomer", "condition": { "op": "true", "values": [true] } }]
},
"steps": [
{
"id": "findCustomer",
"action": "kustomer.customer.find-by-email",
"appVersion": "kustomer-^1.9.0",
"params": { "email": "/#steps.1.email" },
"transitions": [
{ "target": "findOrder", "condition": { "op": "exists", "values": ["/#steps.findCustomer.id"] } }
]
},
{
"id": "findOrder",
"action": "kustomer.kobject.find-by-external-id",
"appVersion": "kustomer-^1.9.0",
"params": { "klassName": "acme_orders-order", "externalId": "/#steps.1.orderId" },
"transitions": [
{ "target": "updateOrder", "condition": { "op": "exists", "values": ["/#steps.findOrder.id"] } },
{ "target": "createOrder", "condition": { "op": "true", "values": [true] } }
]
},
{
"id": "updateOrder",
"action": "kustomer.kobject.update",
"appVersion": "kustomer-^1.9.0",
"params": {
"id": "/#steps.findOrder.id",
"klassName": "acme_orders-order",
"title": "Order #{{steps.1.orderId}}",
"data": {},
"custom": { "statusStr": "/#steps.1.status", "totalNum": "/#steps.1.total" }
},
"transitions": []
},
{
"id": "createOrder",
"action": "kustomer.kobject.create-with-customer",
"appVersion": "kustomer-^1.9.0",
"params": {
"customer": "/#steps.findCustomer.id",
"klassName": "acme_orders-order",
"externalId": "/#steps.1.orderId",
"title": "Order #{{steps.1.orderId}}",
"data": {},
"custom": { "orderNumberStr": "/#steps.1.orderId", "statusStr": "/#steps.1.status", "totalNum": "/#steps.1.total" }
},
"transitions": []
}
]
}
]
}Because a Kustomer action that finds nothing succeeds with an empty output, the exists checks route each run down the right branch. See How Kustomer actions succeed and fail.
Read settings in workflows
Use the setting helper in any template to read the installing org's values:
{ "params": { "uri": "https://{{{ setting acme_orders.default.storeDomain }}}/api/orders" } }Keep secrets out of workflow templates: workflow JSON and run logs are visible to admins. Reference secret settings from an action's appSettings instead, where they're filled in at request time.
Let admins customize your workflows
Admins often want to tweak an app workflow. Rather than editing it (an update to your app would overwrite their change), they copy it and set replaces on the copy to your workflow's name. While their copy is enabled, yours is skipped. See Override an app workflow.
Build workflows in the editor first
Writing large workflows by hand is slow. A faster loop:
Register a version with your actions and triggers
Ship
hooks,triggers,actionsandsettings, without workflows.Build in the workflow builder
Install it in a sandbox and build the workflow visually, using your app's actions and trigger.
Export the JSON
Read it with
kusty api GET /v1/workflows/<id> --output json, remove the read-only attributes, and replace hard-coded values withsettinghelpers.Ship it
Add it to
workflows, give it a name unique to your app, bump the version and register.
Outbound webhooks
outboundWebhooks subscribes your service to Kustomer events in every org that installs the app, with no setup by the admin:
{
"outboundWebhooks": [
{
"name": "Acme order sync",
"url": "https://hooks.acme.example/kustomer",
"events": ["kustomer.conversation.create", "kustomer.conversation.update"],
"headers": [{ "name": "x-acme-source", "value": "kustomer" }]
}
]
}namestringrequired- Shown to admins in the org's outbound webhooks list.
urlURLrequired- Where Kustomer posts each event.
eventsstring[]required- Event names, such as
kustomer.customer.updateorkustomer.message.create. See the event catalog. headersobject[]- Extra headers as
{ name, value }pairs.
Every org posts to the same url. Use the org field in each payload to tell them apart. Payloads, signatures, retries and delivery history work the same as for any outbound webhook.