Triggers and exits
How a flow starts and stops, field by field: the trigger event and the one source it should listen to, Instasent's own events, the event filter, who may enter, the re-entry limits, the exits that take a contact out, and how goals are counted.
Every flow has exactly one trigger, the root of the graph, and through the API it is always TRIGGER_CONTACT_EVENT: the flow starts when a contact produces an event. Whether a flow works depends less on the shape of the graph than on this node: whether its event really arrives, from which source, and what stops the journey once its purpose is met.
A trigger that fires on a schedule exists in the product but cannot be placed through the API; a draft carrying one is refused.
The event and its source
eventTypestringrequiredThe event type uid, such as ecommerce_checkout_init. Take it from the event catalogue or from the event volumes probe, never from a display name. Only types whose automation is true in the catalogue can start a flow.
eventDatasourcestringThe id of the one data source the trigger listens to. null listens to every source.
Point the trigger at the event's preferred source. When two sources send the same event — a shop and a marketing tool that relays the shop's orders, for example — they are copies of one fact, and nothing merges them. A trigger with no source fires once per copy, so the same contact enters the flow twice for one purchase.
The event volumes probe lists, for each event type, the sources that sent it, most authoritative first, and marks at most one of them preferred. For commerce events the shop that owns the orders outranks a tool that relays them; for anything else the fresher and busier source wins. Set eventDatasource to that id. The templates do this for you.
Some events are late by design. A shop's ecommerce_checkout_abandon is typically raised hours after the checkout stops changing, so a reminder triggered on it already carries that delay. Triggering on ecommerce_checkout_init and adding your own delay, with an exit on the order, is the faster and more common choice.
Instasent's own events
Some events are raised by the platform itself rather than by a source: the contact changed, subscribed, unsubscribed, clicked a tracked link. The probe lists them apart, in platformEventTypes, and they are the one case where eventDatasource stays null: they are raised on whatever source the contact belongs to, so no source is preferred.
| Event | Starts a flow when |
|---|---|
update | The contact changed. Filtered on update.new-contact being true, it is a contact created in that source: the welcome trigger, not subscribe. Filtered on update.lists-added or update.tags-added (the exact list or tag name), it is "joined list X" or "gained tag Y"; lists-lost and tags-lost are the reverse. |
subscribe, unsubscribe | The contact's consent changed. |
campaign_cta, automation_cta, flow_cta, transactional_cta | A tracked link in a message was clicked. |
Sends, opens, contact creation, merges and deletions are recorded as events too, but they cannot start a flow.
The event filter
eventQueryFilter narrows the trigger to some occurrences of the event, by the event's own parameters. An empty value ([]) means every occurrence. Otherwise it is one root group in the event query filter grammar, and every key is <eventType>.<parameter>:
{
"root": {
"type": "group",
"join": "and",
"children": [
{
"type": "event_condition",
"key": "ecommerce_order_create.order-euro-amount",
"operator": "range-number",
"values": { "lowerNumber": 100 }
}
]
}
}Because every key is prefixed with the event type, a filter belongs to one event: change eventType and rewrite the filter.
Filter only on values that actually arrive. Probe the event first with "event": "<eventType>" on /event/volumes: it lists the real values of each parameter and its fillRate, the share of events that carry it at all. A filter on a value that never occurs, or on a parameter the source never fills (product tags are empty on many stores), produces a flow that is valid and never fires.
Who may enter
audienceQueryFilterobjectAn audience query filter the contact must match when entering. Empty means everyone. To use a segment, carry its uid in metadata.segment.uid next to its root: a filter tied to a segment is refreshed from that segment when the flow is published and while it runs, while one without a uid is kept exactly as written. The templates use dyn-generic-has-phone this way, so nobody without a mobile phone enters a journey that can never send.
excludeQueryFiltersobject[]Filters in the same shape. A contact matching any of them does not enter.
cancelIfExitsAudienceSegmentbooleanWhen true, a contact who stops matching the audience while inside the flow, exclusions included, is taken out.
Segment uids come from GET /segment.
Re-entry and caps
A contact who hits any of these limits does not enter, and the attempt is dropped, not queued.
maxActivationsPerContactintegerEntries per contact over the flow's lifetime, across every version. 0 means unlimited; 1 means once ever, which is what a welcome wants.
minSecBetweenActivationsPerContactintegerSeconds that must pass since this contact last entered. A checkout reminder uses a day, so it does not run twice for the same shopper in one afternoon.
maxConcurrentActiveFlowsPerContactintegerRuns of this flow one contact may have in progress at once. It is not a limit across flows: two flows on the same trigger both run.
maxActivationsPerHourintegerEntries into the whole flow per hour, all contacts together.
maxActivationsPerDayintegerEntries into the whole flow per day, all contacts together.
The plan also applies its own ceiling on entries when the flow runs, so the effective cap can be lower than the value stored.
Entry is not instantaneous: each entry waits a short moment so the flow reads the contact as it is after the trigger event, and under heavy traffic entries may be held a little longer — never beyond the waits the flow already has before its first send, so send times do not move.
Exits
Exits live on the trigger, in exitSteps, never in the chain of steps. Each one is an ACTION_SEQUENTIAL_EXIT_TRIGGER_EVENT with the same fields as the trigger — eventType, eventDatasource, eventQueryFilter — and the same rules: prefix filter keys with the exit's own event type, and point it at the same preferred source as the trigger, or it will also fire on relayed copies. A trigger holds at most 10 exits.
{
"stepClass": "TRIGGER_CONTACT_EVENT",
"eventType": "ecommerce_checkout_init",
"eventDatasource": "66c3a1b2e5a6b7c8d9e0f1a2",
"exitSteps": [
{
"stepClass": "ACTION_SEQUENTIAL_EXIT_TRIGGER_EVENT",
"eventType": "ecommerce_order_create",
"eventDatasource": "66c3a1b2e5a6b7c8d9e0f1a2",
"eventQueryFilter": [],
"useAsGoal": true
}
],
"maxConcurrentActiveFlowsPerContact": 1,
"minSecBetweenActivationsPerContact": 86400,
"nextStep": { "...": "..." }
}- An exit event received while the contact is inside the flow ends the run. Nothing further is sent.
- An exit event that already arrived when the contact is about to enter keeps them out. Since entry happens shortly after the trigger rather than at the same instant, a shopper who orders moments after abandoning a checkout is never messaged.
- An exit on the trigger's own event restarts the clock. The event that started the run never ends it; the next one does, and starts a fresh run if the re-entry limits allow. That is how a winback waits from the contact's last order rather than their first.
Every reminder needs a way out when its purpose happens: a checkout or product journey without an exit on ecommerce_order_create is flagged by the draft advice.
Goals
A goal is how a flow measures that it did its job.
useAsGoal: trueon an exit makes it a goal: the run ends by goal instead of a plain exit. Set it only on exits that are objectives — purchased, booked — and never on housekeeping exits such asunsubscribe, or the goal figures inflate.ACTION_SEQUENTIAL_GOAL, a node in the graph, marks a goal by position: the contact reached this point. It does not send anything or stop the run. See Nodes.
A goal counts only if the run had already sent the contact at least one message; one reached before any message is reported apart. The goal rate is runs with a goal over runs that sent at least one message, so it never exceeds 100%. A goal happens at most once per run and carries no money: revenue and conversions come from attribution, so a flow's goal count and its conversion count can legitimately differ.