Skip to main content

Triggering a flow through the API

A flow is an automation built in the dashboard's visual editor: messages, questions, conditions, waits. The API trigger lets your system start that flow for a contact at the right moment — an order paid, a lead arriving from the website, an invoice overdue — without building each message in code.

Preparing the flow

  1. In the editor, add the API trigger and connect it to the first step.
  2. Optionally, pick on the trigger the number that will send.
  3. Publish and activate the flow. The trigger always uses the published version — a trigger that only exists in the draft answers 422 flow_no_api_trigger.

The flow id shows in the editor and in GET /flows.

Triggering

curl -X POST https://api.joinotify.com/flows/cm1fl0w000001/trigger \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-1042-paid' \
-d '{
"contact": { "phone": "+5541987111527" },
"data": { "order": "1042", "total": "$ 189.90" }
}'
202 Accepted
{ "data": { "runId": "cm1run0000001", "replayed": false } }
  • The contact comes as contact.id or contact.phone. With the phone, a contact that does not exist is created — and fires contact.created.
  • The data in data is available in the flow as {{trigger.order}}, with an optional default: {{trigger.coupon|no coupon}}.
  • The sending number is phoneNumberId, if you send it; otherwise the one set on the trigger; otherwise the account's oldest active number.

The answer arrives before the messages: the flow runs right after, outside the request.

runId: null is not an error

It happens when the trigger is connected to no step, or when the same contact started this flow a few moments ago — there is a minimum interval between runs, so that a duplicate event in your system does not send everything twice.

Starting a run cancels the contact's other live runs on that number, in any flow. A contact talks to one flow at a time.

Retrying without duplicating

With Idempotency-Key, repeating the trigger within 24 hours returns 200 with the same runId and replayed: true, without starting another run. Two differences from POST /messages:

  • the body is not compared — the same key returns the same run, even with different data;
  • a call that failed (inactive flow, invalid number) does not keep the key, and the repeat tries again.

What is checked later

The trigger accepts the request; the sending rules apply when each message goes out:

  • a marketing template to a contact who opted out or is on the suppression list ends the run with the reason contact_opted_out;
  • a free-form message with the 24-hour window closed ends with window_closed, unless the step has a fallback template configured;
  • if the account loses its entitlement along the way, the run stops with billing.

Following the outcome

Subscribe to two events on your webhook endpoint:

EventWhat it carries
flow.answer.capturedEach answer a Question or Wait-for-reply step stored
flow.run.completedThe end of the run, with every flow variable

The data you sent in data does not come back in the event — keep the runId to match both ends. Runs that fail or are cancelled produce no flow.run.completed. The payloads are in the event catalog.

Limits and scope

  • Each trigger spends one request from your key's sending limit. The messages the flow sends afterwards do not.
  • An API key limited to some numbers can trigger, but only with numbers in its scope. Listing and reading flows needs an unrestricted key.