Send a flow event
Trigger a saved Blueticked flow from a booking system, CRM, website form or other external app.
Body parameters
| business_id | string |
| channel | string |
| eventrequired | string |
| string | |
| data | object |
- Create an API key with the least-privilege
events:sendscope. - The contact must already exist; identify it with exactly one of contact_id, email, or phone.
- Phone accepts international or South African national forms, spaces and punctuation, normalized by the callback's rule. This endpoint never creates a contact or grants consent.
- HTTP 202 means accepted, not provider delivery. Read outcome.kind and outcome.outcomes (flowId, channel, kind, reason, messageIds): queued means message rows were queued, even when a later step was then refused (the refusal is kept in the flow's run history); not_sent is a refusal or failure, and a line that failed after making message rows still lists their ids in messageIds, some of which may have gone; no_flow means no listener; ran means non-send actions ran. Dry-run evaluation returns 200 with outcome: null and queues nothing. Free-text WhatsApp needs an open 24-hour reply window; an approved template is required outside it.
- channel (email, sms or whatsapp) selects which flows fire. Omit it and your workspace's own live flows decide: the one channel whose flow matches this event and can reach this contact fires, and the response tells you which. When flows on more than one channel qualify, the request fails with validation_failed, and that refusal is built for programmatic recovery: error.details[0].candidates lists the qualifying channels and error.next_steps[0].action is
choose_channel, so branch on the action, pick a candidate, and retry with channel set. Never parse the message sentence. There is no silent default, ever; the contact identifier does not choose the channel. events:sendis a *trigger* scope, not a send scope: it fires the flows your workspace has already configured, and those flows are the send authority, so this key never needsmessages:sendor a per-channel send scope, even though a flow may end up sending. A fired event can only run pre-authored flows for an existing contact; it cannot choose the message content, template or recipient. (This mirrors triggering a Twilio Studio flow or a Stripe workflow: the trigger credential doesn't carry the flow's send rights.)- While integrating, add test_mode: true (or use a blu_test_ key) to evaluate the flow without sending live messages. The dry-run responds 200 (not 202) with test_mode: true and a matched_filter_label alongside the matched filter and action ids. A dry run needs no connected channel: evaluation sends nothing, so it works before WhatsApp or email is set up.
- Use an Idempotency-Key header so external retries do not fire duplicate actions.
curl -X POST "https://app.blueticked.com/api/v1/events/send" \
-H "Authorization: Bearer $BLUETICKED_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: change-me-per-request-0001" \
-d '{
"business_id": "22222222-2222-4222-9222-222222222222",
"channel": "email",
"event": "appointment_requested",
"email": "sam@example.com",
"data": {
"service": "Consultation",
"preferred_date": "2026-07-01"
}
}'Response
{
"accepted": true,
"handled": true,
"outcome": {
"kind": "queued",
"outcomes": [
{
"flowId": "7cd00b02-a9c7-4a24-bf34-a870305f214a",
"channel": "email",
"kind": "queued",
"reason": null,
"messageIds": [
"763c4dc3-d46b-4c10-8bd4-3f233a933ea0"
]
}
]
},
"event": "appointment_requested",
"channel": "email",
"business_id": "22222222-2222-4222-9222-222222222222",
"contact_id": "ff506e53-e2f0-4d92-884a-dab77d628bad",
"matched_filter_id": "api-event-appointment",
"action_ids": [
"send-confirmation"
],
"request_id": "req_1f138f2b80962e12456bea33de951bb5"
}