Triggers and events
In short: every workflow starts on exactly one kind of event (the trigger), optionally narrowed down with conditions. This article is the reference: per event the technical name, the label in the editor, when it fires, all fields it passes along (with type and choice values) and the content fields a step can read. After that comes exactly how trigger conditions compare.
Where to find this
Account > Workflows, open a workflow. At the top is the When block with the This happens dropdown (the event), Only if (the conditions) and Never twice at once for (the concurrency key).
Who: from the role Organisation admin, for the workflows of an organisation whose AI settings you manage. Only an Account admin can create or change a workflow for the whole account.
The When block of an opened workflow under Account > Workflows, with the This happens dropdown, the Only if block with a condition (Field, operator, Compare with) and the Never twice at once for field.
How a trigger works
- A workflow has one trigger:
{"event": "<event>", "conditions": [...]}. In the editor you choose the event under This happens. - For every event of that kind, aishaa checks the conditions. If they are all met (or there are none), a run starts on the live version of the workflow.
- Every workflow whose trigger matches starts. There is no order or priority between workflows and no workflow passes the work on to another.
- At most one run starts per workflow and event.
- A workflow never reacts to an event it caused itself.
- An event that is 5 or more links deep in a chain of automations no longer starts any work. The run is then recorded as stopped, to prevent a loop.
- A workflow of one organisation only reacts to events of that organisation. A workflow of the whole account reacts to events of all organisations of the account.
- An event only contains references (ids) to tickets, messages, customers and records, never their content. You read the content in a step through the content fields (see below).
Overview of all events
| Technical name | Label in the editor | When |
|---|---|---|
message.received |
A message comes in | A customer or someone outside the organisation sends a live message in a conversation. |
message.understood |
Message read | aishaa has read and understood a customer message. The moment to sort and answer it. |
action.resolved |
An action is handled | A proposal, request or task leaves the “waiting” status: carried out, failed, rejected, answered or expired. |
object.linked |
Something is linked to a customer | A record (an order, a dossier) or something aishaa keeps itself now belongs to a customer. |
object.unlinked |
A link to a customer is removed | Something no longer belongs to a customer. |
recording.transcribed |
A conversation is transcribed | A recorded conversation with a customer has been transcribed. |
ticket.created |
A conversation starts | A customer opens a new conversation with a first message, or a colleague starts one. |
ticket.status_changed |
A conversation’s status changes | The status of a conversation changes, by a colleague, a rule or aishaa. |
ticket.assigned |
A conversation is assigned | A conversation gets a (different) colleague, or nobody any more. |
schedule.due |
On a schedule | The workflow’s own schedule comes round, for example every Monday at 9:00. |
Events a customer is waiting for (message.received and message.understood) run with priority. All others run in the background.
Field types
Every field has a type. It determines how a condition compares it and how you use it.
| Type | Meaning |
|---|---|
id |
A number or code that points to something (a ticket, a colleague, a customer). |
string |
Text. If there is a list of choice values, it is always one of those values (technical name, written case-sensitively, but a condition compares case-insensitively). |
number |
A number. |
boolean |
true or false. |
moment |
A date and time (ISO 8601). |
Fields marked content are not part of the event itself. aishaa reads them at the moment a step uses them (see “Content fields”).
message.received: A message comes in
Fires when a message from outside the organisation comes in live in a conversation: from the customer or from an external participant. Not for imported history and not for messages the organisation sends itself. At this moment aishaa has not read the message yet. If you want to steer on the meaning of the message (topic, urgency, whether a reply is needed), choose message.understood.
| Field | Label | Type | Choice values |
|---|---|---|---|
trigger.ticketId |
Ticket | id | |
trigger.messageId |
Message | id | |
trigger.channel |
Channel | string | Unknown, Email, WhatsApp, WebWidget, Instagram, TwitterX, Sms, PhoneCall |
trigger.senderType |
Sender | string | Agent, Customer, AutoReply, ExternalParticipant |
trigger.agentUserId |
Registered by colleague | id | The colleague who registered the message on behalf of the customer, if so. |
trigger.automatedIntelligenceSuppressed |
Without automatic AI | boolean | true if no automatic AI was requested for this message when it came in. |
Content fields: trigger.ticket.* and trigger.message.* (see “Content fields”).
message.understood: Message read
Fires after aishaa has read and understood an incoming customer message: the question, the topic, the urgency and whether a reply is needed. This is the event on which you sort and answer customer messages. The blocks agent.reply (Write the reply) and case.process (Update cases) only work in a workflow with this trigger.
| Field | Label | Type | Choice values and explanation |
|---|---|---|---|
trigger.ticketId |
Ticket | id | |
trigger.messageId |
Message | id | The message that was read. |
trigger.aiRequestId |
Analysis | id | The analysis of the message. An agent.reply step completes it. |
trigger.channel |
Channel | string | Unknown, Email, WhatsApp, WebWidget, Instagram, TwitterX, Sms, PhoneCall |
trigger.senderType |
Sender | string | Agent, Customer, AutoReply, ExternalParticipant |
trigger.replyNeed |
Reply needed | string | Required, NotRequired, Unclear |
trigger.replyNeedReason |
Why no reply | string | Only filled with NotRequired: closing_remark, delivery, notification, handled_elsewhere or unwanted. |
trigger.replyNeedConfidence |
How sure (reply needed) | number | 0 up to and including 100. |
trigger.language |
Language | string | The language to reply in, such as nl-NL. |
trigger.question |
Question | string | The customer’s question in one sentence. |
trigger.urgent |
Urgent | boolean | aishaa found the message urgent (a deadline, a safety risk, a customer who cannot continue). |
trigger.urgencyReason |
Why urgent | string | In one sentence; empty if it is not urgent. |
trigger.topic |
Topic | string | The support area of the main question on the organisation’s topic map; empty if it has not been placed. |
trigger.topicDetail |
Kind of question | string | The kind of question within that topic; empty if it has not been placed. |
trigger.topicId |
Topic id | id | The id of that topic. Use this to route on something that does not change when a name changes. |
trigger.topicDetailId |
Kind of question id | id | The id of that kind of question. |
trigger.topicConfidence |
How sure of the topic (0-100) | number | How sure the placement is. |
trigger.isNewConversation |
New conversation | boolean | The message opened the conversation (the first message). |
trigger.withinOpeningHours |
Within opening hours | boolean | The message came in within the organisation’s opening hours. |
Content fields: trigger.ticket.* and trigger.message.*. The text of the message is trigger.message.text.
Good to know about message.understood:
- The event fires for a message that came in by itself. If a colleague asks for a proposal themselves, no new workflow run starts.
- If the customer writes again before the previous message has been answered, the open answering runs of the older message stop and the new message takes over.
- If reading the message fails, the event does not fire.
action.resolved: An action is handled
Fires when an action is no longer waiting: a proposal has been carried out, has failed or has been rejected, a request to a person has been answered, a task has been done, or something has expired.
| Field | Label | Type | Choice values and explanation |
|---|---|---|---|
trigger.actionId |
Action | id | |
trigger.kind |
Kind | string | Plugin, ExternalMessage, ScheduledWake, TicketChange, Request, Task, CustomerMessage, WhatsAppTemplate |
trigger.pluginId |
Plugin | string | The plugin of the action. |
trigger.operationId |
Plugin action | string | The action within the plugin. |
trigger.status |
Status | string | Pending, Executed, Failed, Rejected, Expired, Superseded |
trigger.outcome |
Answer | string | The answer a person chose for a request; otherwise empty. |
trigger.inputEdited |
Edited by a person | boolean | A person edited the parameters before it was carried out. |
trigger.resolvedByUserId |
Handled by | id | Who carried it out, rejected it or answered; empty with automatic handling. |
trigger.ticketId |
Ticket | id | Empty if the action does not belong to a ticket. |
trigger.workflowRunId |
Workflow run | id | The run that was waiting for this action, if there was one. |
trigger.workflowStepSequence |
Workflow step | number | The step number in that run. |
Content fields: trigger.ticket.*.
A workflow that is itself waiting for a request, proposal or message does not need this event: the waiting step continues by itself as soon as the action has been handled.
object.linked: Something is linked to a customer
Fires once per new link: a lookup in a connected system found a record for the customer, or a person, aishaa or a workflow linked something. Not when a source confirms an existing link again.
| Field | Label | Type | Choice values and explanation |
|---|---|---|---|
trigger.objectType |
Object type | string | record for a record from a connected system, otherwise the kind of something aishaa keeps itself. |
trigger.objectKey |
Object | string | The id of the object. |
trigger.partyId |
Customer | id | Empty if the link belongs to the ticket (a conversation without a customer). |
trigger.ticketId |
Ticket | id | |
trigger.source |
Source | string | Lookup (found by the lookup), Person (by a person), Ai (by aishaa, a person can undo it), Workflow (by a workflow step) |
trigger.strongEvidence |
Found on e-mail address or phone number | boolean | Found on the customer’s e-mail address or phone number, or linked by a person. |
trigger.linkedByUserId |
Linked by | id | |
trigger.recordKind |
Record type | string | For a record: the kind as the plugin calls it (for example order, dossier). |
trigger.pluginId |
Plugin | string | For a record: the plugin it comes from. |
Content fields: trigger.record.* (only filled if the object is a record), trigger.customer.* and trigger.ticket.*.
object.unlinked: A link to a customer is removed
Fires when the link of an object with a customer ends: a person undid it, or whatever made it has been withdrawn.
| Field | Label | Type | Choice values and explanation |
|---|---|---|---|
trigger.objectType |
Object type | string | As with object.linked. |
trigger.objectKey |
Object | string | |
trigger.partyId |
Customer | id | |
trigger.ticketId |
Ticket | id | |
trigger.source |
Source | string | How the removed link had come about: Lookup, Person, Ai, Workflow |
trigger.unlinkedByUserId |
Removed by | id | |
trigger.recordKind |
Record type | string | |
trigger.pluginId |
Plugin | string |
Content fields: trigger.record.*, trigger.customer.* and trigger.ticket.*.
recording.transcribed: A conversation is transcribed
Fires when a recorded conversation with a customer has been transcribed, with the speakers told apart. What happens next (a report, a task, the dossier) you build in the workflow. You read the text directly as trigger.recording.transcript.
| Field | Label | Type | Explanation |
|---|---|---|---|
trigger.recordingId |
Recording | id | |
trigger.partyId |
Customer | id | Empty as long as nobody has linked the recording to a customer. |
trigger.durationSeconds |
Duration (seconds) | number | |
trigger.language |
Language | string | |
trigger.speakers |
Number of speakers | number | How many people the transcript tells apart. |
trigger.recordedByUserId |
Recorded by | id | Useful as the assignee of a request or task: {{trigger.recordedByUserId}}. |
Content fields: trigger.recording.* and trigger.customer.*.
ticket.created: A conversation starts
Fires when a conversation starts: a customer’s first live message opened it, or a colleague started it. Not for imported history or restored conversations.
| Field | Label | Type | Explanation |
|---|---|---|---|
trigger.ticketId |
Ticket | id | |
trigger.partyId |
Customer | id | |
trigger.channel |
Channel | string | Unknown, Email, WhatsApp, WebWidget, Instagram, TwitterX, Sms, PhoneCall |
trigger.createdByUserId |
Started by (colleague) | id | Empty if the customer opened the conversation. |
trigger.firstConversation |
The customer’s first conversation | boolean | The customer had no earlier conversation yet. Useful for welcoming a new customer. |
Content fields: trigger.ticket.* and trigger.customer.*.
ticket.status_changed: A conversation’s status changes
Fires when the status of a conversation changes (closed, reopened, answered, set aside), by a colleague, a rule or an aishaa task. Not for imported history.
| Field | Label | Type | Choice values and explanation |
|---|---|---|---|
trigger.ticketId |
Ticket | id | |
trigger.partyId |
Customer | id | |
trigger.status |
Status | string | The new status: Open, Answered, Closed, Concept, Pending, Error, Cancelled, Snoozed, WaitingExternal |
trigger.previousStatus |
Previous status | string | The old status, same values. |
trigger.changedByUserId |
Changed by (colleague) | id | Empty if a rule or task changed the status. |
Content fields: trigger.ticket.* and trigger.customer.*.
ticket.assigned: A conversation is assigned
Fires when a conversation is assigned to a colleague, to a different colleague, or to nobody.
| Field | Label | Type | Explanation |
|---|---|---|---|
trigger.ticketId |
Ticket | id | |
trigger.partyId |
Customer | id | |
trigger.assigneeUserId |
Assigned to (colleague) | id | Empty (missing) if the conversation is assigned to nobody. |
trigger.previousAssigneeUserId |
Previously assigned to | id | |
trigger.changedByUserId |
Changed by (colleague) | id | Empty if a rule or task assigned it. |
Content fields: trigger.ticket.* and trigger.customer.*.
schedule.due: On a schedule
Fires when the workflow’s own schedule comes round. This event only starts the workflow the schedule belongs to, never another one. It mentions nothing else: a scheduled workflow looks up what it works with in its own steps.
| Field | Label | Type | Explanation |
|---|---|---|---|
trigger.workflowId |
Workflow | id | The workflow whose moment it is. |
trigger.scheduledFor |
Planned moment | moment | The moment from the schedule. The run may start a minute later. |
Content fields: none.
The schedule is in the trigger itself, as trigger.schedule:
| Part | Values |
|---|---|
every |
hour, day, weekday (Monday to Friday), week or month. Required. |
at |
A time as HH:mm, such as 09:00. Required. With hour, only the minutes count. |
days |
Only with week, required: one or more of monday, tuesday, wednesday, thursday, friday, saturday, sunday. |
dayOfMonth |
Only with month, required: 1 up to and including 28. |
timeZone |
Optional, a time zone such as Europe/Amsterdam. Empty: the organisation’s own time, otherwise the account’s. |
A trigger other than schedule.due may not have a schedule. More about setting it up in the editor is in Scheduled workflows.
Content fields
An event only refers to tickets, messages, customers, recordings and records by their id. aishaa offers the content as extra fields under trigger, grouped per object. You refer to them directly in a step, for example {{trigger.message.text}}; no separate step is needed to fetch them.
| Object (label) | Fields | With events |
|---|---|---|
trigger.ticket (Ticket) |
number (number, Ticket number), title (string, Title), status (string, Status, values as above), channel (string, Channel), urgent (boolean, Urgent), assigneeUserId (id, Assigned colleague), assigneeName (string, Name of the assigned colleague), customerName (string, Customer’s name), customerEmail (string, Customer’s e-mail address), customerPhone (string, Customer’s phone number), createdAt (moment, Created at) |
message.received, message.understood, action.resolved, object.linked, object.unlinked, ticket.created, ticket.status_changed, ticket.assigned |
trigger.message (Message) |
text (string, Text), senderName (string, Sender), channel (string, Channel), sentAt (moment, Time) |
message.received, message.understood |
trigger.customer (Customer) |
name (string, Name), email (string, E-mail address), phone (string, Phone number), language (string, Language), createdAt (moment, Created at) |
object.linked, object.unlinked, recording.transcribed, ticket.created, ticket.status_changed, ticket.assigned |
trigger.recording (Conversation) |
title (string, Title), transcript (string, Transcript), createdAt (moment, Created at) |
recording.transcribed |
trigger.record (Record) |
title (string, Title), subtitle (string, Subtitle), reference (string, Reference), kind (string, Record type), itemId (string, Number in the system), recordId (id, Record) |
object.linked, object.unlinked |
This is how content fields work:
- aishaa only reads the content when a step uses the field, and only for that step. A step after a wait step sees the content as it is at that moment.
- aishaa only reads within the account and the organisation of the run.
- If the object has disappeared, the fields are empty. You can test that with
missingin a step. - A customer’s e-mail address and phone number are the primary address and number, otherwise the oldest.
trigger.ticket.channelis the channel of the last incoming message, otherwise the channel the conversation started on.- A trigger condition cannot read a content field. Test content in a
flow.condition(Condition) orflow.switch(Choose a route) step.
Trigger conditions
A condition is {"path": "trigger.<field>", "op": "<operator>", "value": <value>}. In the editor the block is called Only if, with Field, the operator and Compare with.
- All conditions must be met (and). There is no “or”. If you want to react to one thing or another, use a
flow.switchorflow.conditionstep in the workflow, or create two workflows. - Without conditions the workflow reacts to every event of the kind.
- A trigger condition only reads the event’s own fields (the tables per event above). The path must start with
trigger.. A content field such astrigger.message.textortrigger.ticket.customerEmailgives an error: deciding whether a workflow starts must cost nothing. Test content in a step. - Conditions cost nothing (no AI is involved). That is why the editor can show, for the latest events, which condition stopped a start.
Operators
| Operator | Label | Met if | value |
|---|---|---|---|
eq |
is | the field is equal to the value | required |
neq |
is not | the field is not equal to the value (also if the field is missing) | required |
gt |
greater than | the field is greater than the value | required |
gte |
at least | the field is greater than or equal to the value | required |
lt |
less than | the field is less than the value | required |
lte |
at most | the field is less than or equal to the value | required |
contains |
contains | the text contains the value; if the field is a list, if an item of the list is equal to the value | required |
exists |
is filled | the field is there and is not empty (null) |
none |
missing |
is empty | the field is missing or is null |
none |
Write the operator in lower case, exactly as in the table.
How it compares
- Numbers as numbers. If the field and the value are both a JSON number, aishaa compares them as numbers. So write a number without quotation marks:
"value": 80, not"value": "80". With quotation marks it is compared as text, and then"9"is greater than"10". - Moments as moments. If both values can be read as a date and time, aishaa compares them as moments.
- The rest as text, case-insensitive.
"whatsapp"is equal toWhatsApp. Yes/no fields compare as the texttrueorfalse;"value": trueworks. - A missing field is equal to nothing.
eq,gt,gte,lt,lteandcontainsare then not met;neqis met;missingis met. - An empty text (
"") counts as filled forexists. containson text looks for a piece of text, case-insensitive:contains"retour"is also met for"Retourzending".
Examples
Only new WhatsApp conversations outside opening hours:
{
"event": "message.understood",
"conditions": [
{ "path": "trigger.channel", "op": "eq", "value": "WhatsApp" },
{ "path": "trigger.isNewConversation", "op": "eq", "value": true },
{ "path": "trigger.withinOpeningHours", "op": "eq", "value": false }
]
}
Only conversations that are being closed:
{
"event": "ticket.status_changed",
"conditions": [ { "path": "trigger.status", "op": "eq", "value": "Closed" } ]
}
Only recordings that are already linked to a customer:
{
"event": "recording.transcribed",
"conditions": [ { "path": "trigger.partyId", "op": "exists" } ]
}
Never twice at once (concurrencyKey)
With concurrencyKey (in the editor Never twice at once for) you give a path in the trigger, usually trigger.ticketId. If a new event comes in with the same value while a run of this workflow is still open (running, waiting or paused), that event starts nothing. If the value is empty, there is no limit. The key too must be one of the event’s own fields, not a content field. More in References, routing, repeating and choosing.
Good to know
- Every event has one fixed shape. Build conditions on the technical names and values from this article; the labels are what people read in the editor.
- You can use an
idfield of a colleague (such astrigger.recordedByUserIdortrigger.assigneeUserId) as theassigneeof a request or task. - For customer messages, almost always choose
message.understood: then the workflow already knows what is being asked and anagent.replystep can answer. - Under Field, the editor also has the choice Other field… for a path that is not in the list.
