Triggers
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
Triggers
Rule-based automations that fire on domain events — your one-line "when X happens, do Y" engine.
A trigger is the simplest unit of automation in Chatly: when X happens, do Y. Triggers are single-step and run as soon as the event reaches the worker. Anything that needs branching, waiting, or multi-step state belongs in a workflow instead.
Info — The 80/20 rule of automation
~80% of useful automations are one-step: route by tag, assign by plan, post to Slack, fire a webhook. Don't reach for workflows until you need branching or waits — triggers are simpler, faster, and easier to debug.
Event types
eventType on a trigger is matched exactly against the type of the event envelope on the bus. Triggers and workflows share one catalog of 66 event keys, so anything you can start a workflow on you can also hang a trigger on. The most-used ones:
Common trigger event types
Name | Type | Description |
|---|---|---|
|
| A brand-new conversation. The most common trigger event. |
|
| Status moved (open / snoozed / closed). There is no generic |
|
| Routing outcomes. |
|
| Lifecycle. |
|
| Metadata changes. |
|
| Any new message. Narrower variants exist and are usually what you want: |
|
| CDP-driven contact lifecycle. Note the name is |
|
| Contact bookkeeping. |
|
| Widget-side signals. These are the only visitor events forwarded onto the bus. |
|
| Audience membership changed. Published by the hourly audience sweep — see Audiences. |
|
| Clock-driven. Published by the scheduler tick each minute. |
|
| Ticketing lifecycle (11 keys). |
|
| Email tracking, from provider webhooks. |
|
| Voice (6 keys). |
|
| AI agent outcomes. |
|
| Integration health. |
|
| The remainder. |
Info — The event picker is the source of truth
Automation → Triggers → New lists every key we publish, with a description each. A CI gate fails the build if the picker ever offers an event nothing publishes, so if it is in the dropdown, something emits it.
Conditions
Conditions narrow when a trigger fires. Every condition in the list must match (boolean AND). A trigger carries at most 50.
Supported operators
Name | Type | Description |
|---|---|---|
|
| Strict equality / inequality against the resolved value. |
|
| Numeric only — both the payload value and your |
|
| Set membership. |
|
| Case-sensitive substring on a string. It does not test array membership — for "has this tag", use |
|
| Regex, compiled with the JavaScript |
Those five names plus neq are the whole vocabulary: eq, neq, in, gt, lt, contains, matches. Anything else is rejected by the API with a 400.
Warning — No inline regex flags
matchescompiles withnew RegExp(pattern), which is JavaScript, not PCRE. Inline flag groups like(?i)refundare a syntax error there, and a pattern that will not compile takes the whole trigger dispatch down for that event. Write a case-insensitive match as a character class —[Rr]efund— or normalise upstream.
Field paths are dotted into the event payload:
{ "field": "contact.attributes.plan", "op": "eq", "value": "business" }
{ "field": "message.body", "op": "matches", "value": "[Rr]efund" }
{ "field": "conversation.channel.type", "op": "in", "value": ["whatsapp", "sms"] }
{ "field": "conversation.priority", "op": "eq", "value": "high" }A path that does not resolve yields undefined, which fails every operator except neq.
Actions
A trigger carries between 1 and 20 actions and runs them in order. Each runs the same handler a workflow step of that type would run. If one throws, the failure is logged and the remaining actions still run.
There are 62 action types. Triggers and workflows share the registry, so the builder's action list for a given event is the authoritative menu. The ones you will reach for most:
Frequently used actions
Name | Type | Description |
|---|---|---|
|
| Reply in the conversation. Separate types exist for the other message kinds: |
|
| Assign to a specific agent. For team routing use |
|
| Apply or remove a conversation tag. The removal action is |
|
| Set conversation priority. |
|
| Conversation lifecycle. |
|
| Write or clear a contact attribute. |
|
| Signed HMAC POST to a URL you own. |
|
| Arbitrary HTTP call with a retry policy. |
|
| Out-of-band notification. The Slack action is |
|
| Hand off to a workflow when you outgrow one step. |
|
| Conversation-scoped AI. Text-in/text-out variants: |
|
| File work in an external tracker. There is no generic |
|
| The rest of the integration surface. |
Warning — Action params are not templated
A workflow interpolates
{{trigger.…}}tokens in a step's parameters before running it. A trigger does not. Itsparamsare handed to the action verbatim, so a body containing{{contact.firstName}}is delivered with those braces still in it. If you need per-recipient substitution, useenqueue_workflowand put the message step there.
Example
"When a Business-plan customer mentions 'refund' on the WhatsApp channel, tag the conversation, route to retention, and drop an internal note for the assignee."
Dashboard
Automation → Triggers → New:
Name:
Business refund escalationEvent:
message.contact_replyConditions (all must match):
contact.attributes.planeqbusinessmessage.bodymatches[Rr]efundconversation.channel.typeeqwhatsapp
Actions:
Tag →
refund-requestAssign to team →
retentionSend internal note → "Customer mentions refund. SLA: 4h."
API
POST /v1/triggers — Bearer token
POST /v1/triggers both creates and updates: include id to update an existing trigger, omit it to create. enabled is required — there is no default, and omitting it fails validation.
{
"name": "Business refund escalation",
"eventType": "message.contact_reply",
"enabled": true,
"priority": 10,
"conditions": [
{ "field": "contact.attributes.plan", "op": "eq", "value": "business" },
{ "field": "message.body", "op": "matches", "value": "[Rr]efund" },
{ "field": "conversation.channel.type", "op": "eq", "value": "whatsapp" }
],
"actions": [
{ "type": "tag", "params": { "tag": "refund-request" } },
{ "type": "assign_team", "params": { "teamId": "team_01H7..." } },
{ "type": "send_internal_note", "params": {
"body": "Customer mentions refund. SLA: 4h."
} }
]
}GET /v1/triggers — Bearer token
DELETE /v1/triggers/{id} — Bearer token
The list endpoint is cursor-paginated and accepts ?sort=, ?fields= and ?filter[]= on enabled, eventType, brandId, createdAt and updatedAt. Delete is a hard delete; the runs it already produced are kept separately.
Warning — These endpoints need a signed-in user
Workspace API keys (
ck_…) are opt-in per route, and the trigger routes are not among the routes that accept one. Ack_key gets a 403 here. Manage triggers with a session token or from the dashboard.
Ordering
Triggers fire in ascending priority order. Lower number = earlier; the default is 0.
Use priorities when triggers chain — "first tag, then route based on tag" works only if the tag trigger has a lower priority than the routing trigger. Two triggers on the same priority have no defined relative order, so don't build a chain that depends on it.
Debounce
A trigger with no debounce fires on every matching event. If that is too often — a chatty webhook, a noisy attribute sync — set a debounce window instead:
"debounceSeconds": 3600,
"debounceKeyTemplate": "{{trigger.contact.id}}"The worker writes a Redis sentinel keyed by dedupe:{workspace}:{trigger}:{key} with SET … NX and the window as TTL, before any action runs — so a debounced fire is a complete no-op, with no message sent and no webhook dispatched.
Parameters
Name | Type | Description |
|---|---|---|
|
| Window in seconds, 1 to 604800 (7 days). Null or absent means no debounce. |
|
| Grouping key. Only |
Leave the template null and the key defaults to contact:<id>, then conversation:<id>, then all — whichever the payload supports first.
Info — Debounce is not idempotency
The sentinel groups similar fires inside a window. It is not keyed by event id, so a worker retry of the same event outside the window will run the actions again. Write anything a trigger POSTs to your own systems so that a repeat is harmless.
Troubleshooting
Warning — Trigger fires but actions don't take effect
Most often: the trigger ran on the right event but a later trigger overrode it (one sets the assignee, the next reassigns). Check the priorities — lower runs first.
Info — Trigger doesn't fire at all
Check the three things that silently produce zero fires, in order:
enabledis true;eventTypematches a key in the event picker exactly; and every condition resolves. The last is the usual culprit — a path that does not exist in the payload reads asundefinedand failseq,gt,containsandmatchesalike.
Warning — A gt / lt condition never matches
Both sides have to be numbers.
"value": "5"is a string and will not compare against5, and no timestamp field compares at all. Send numeric literals, and move date logic into a workflow, where the evaluator understands ISO timestamps.