Guides

Triggers

AdminUpdated Sep 19, 2026

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

conversation.created

event

A brand-new conversation. The most common trigger event.

conversation.status_changed

event

Status moved (open / snoozed / closed). There is no generic conversation.updated — the catalog is specific by design.

conversation.assigned, conversation.unassigned, conversation.transferred

event

Routing outcomes.

conversation.closed, conversation.reopened, conversation.snoozed

event

Lifecycle.

conversation.tagged, conversation.priority_changed

event

Metadata changes.

message.created

event

Any new message. Narrower variants exist and are usually what you want: message.contact_reply, message.agent_reply, message.private_note, message.ai_generated.

contact.created, contact.identified, contact.attribute_updated

event

CDP-driven contact lifecycle. Note the name is contact.attribute_updated, not contact.updated.

contact.tagged, contact.untagged, contact.merged, contact.unsubscribed

event

Contact bookkeeping.

visitor.page_view, visitor.exit_intent, visitor.idle_threshold, visitor.scroll_depth, visitor.returned

event

Widget-side signals. These are the only visitor events forwarded onto the bus.

segment.entered, segment.exited

event

Audience membership changed. Published by the hourly audience sweep — see Audiences.

schedule.cron, schedule.specific_time, schedule.business_hours_start, schedule.business_hours_end

event

Clock-driven. Published by the scheduler tick each minute.

ticket.created, ticket.assigned, ticket.resolved, ticket.sla_warning, ticket.sla_breach, …

event

Ticketing lifecycle (11 keys).

email.opened, email.clicked, email.bounced, email.unsubscribed

event

Email tracking, from provider webhooks.

voice.call_started, voice.call_completed, voice.missed_call, …

event

Voice (6 keys).

bot.escalated, bot.handoff_succeeded, bot.tool_call_failed

event

AI agent outcomes.

integration.connected, integration.disconnected, integration.error

event

Integration health.

survey.response_received, survey.low_csat, kb.article_published, webhook.received, workflow.completed, workflow.failed

event

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

eq, neq

op

Strict equality / inequality against the resolved value.

gt, lt

op

Numeric only — both the payload value and your value must be JavaScript numbers. A date string compares as neither, so a timestamp condition never matches. Use a workflow if you need date comparison.

in

op

Set membership. value must be an array; the payload value must appear in it.

contains

op

Case-sensitive substring on a string. It does not test array membership — for "has this tag", use in against the tag list or move the rule to a workflow.

matches

op

Regex, compiled with the JavaScript RegExp engine.

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

matches compiles with new RegExp(pattern), which is JavaScript, not PCRE. Inline flag groups like (?i)refund are 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

send_message

action

Reply in the conversation. Separate types exist for the other message kinds: send_internal_note, send_template, send_email, send_media, send_carousel, send_quick_replies, send_form.

assign

action

Assign to a specific agent. For team routing use assign_team, assign_round_robin or transfer_to_team; unassign clears it.

tag / remove_tag

action

Apply or remove a conversation tag. The removal action is remove_tag — there is no untag.

set_priority

action

Set conversation priority.

change_status, close_conversation, snooze, apply_sla

action

Conversation lifecycle.

set_attribute / unset_attribute

action

Write or clear a contact attribute.

fire_webhook

action

Signed HMAC POST to a URL you own. fire_zapier and fire_make are the hosted-automation equivalents.

http_request

action

Arbitrary HTTP call with a retry policy.

notify_slack, notify_teams, notify_email, push_notification

action

Out-of-band notification. The Slack action is notify_slack, not post_to_slack.

enqueue_workflow, call_workflow, fire_workflow_for_segment

action

Hand off to a workflow when you outgrow one step.

ai_classify, ai_summarize, ai_translate, ai_suggest_reply, run_autonomous_agent

action

Conversation-scoped AI. Text-in/text-out variants: ai_classify_text, ai_summarize_text, ai_extract.

create_jira_ticket, create_linear_issue, create_github_issue, create_asana_task, create_notion_page, create_hubspot_ticket

action

File work in an external tracker. There is no generic create_ticket — pick the destination.

salesforce_upsert, hubspot_update_deal, stripe_refund, shopify_lookup_customer, calendar_create_event, generate_pdf, report_to_sentry, track_event

action

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. Its params are 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, use enqueue_workflow and 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 escalation

  • Event: message.contact_reply

  • Conditions (all must match):

    • contact.attributes.plan eq business

    • message.body matches [Rr]efund

    • conversation.channel.type eq whatsapp

  • Actions:

    • Tagrefund-request

    • Assign to teamretention

    • Send 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. A ck_ 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

debounceSeconds

integer

Window in seconds, 1 to 604800 (7 days). Null or absent means no debounce.

debounceKeyTemplate

string

Grouping key. Only {{trigger.*}} paths are substituted; anything else renders empty. Colons and control characters in a substituted value become _.

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: enabled is true; eventType matches 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 as undefined and fails eq, gt, contains and matches alike.

Warning — A gt / lt condition never matches

Both sides have to be numbers. "value": "5" is a string and will not compare against 5, and no timestamp field compares at all. Send numeric literals, and move date logic into a workflow, where the evaluator understands ISO timestamps.

Was this page helpful?