Skip to content

Documentation

Build with Engage

Connect customer data, business events, and communication channels to build automated journeys with workflows and AI.

Events

One endpoint, and it is the only one most integrations ever call. An event says a thing happened to a person; workflows decide what that is worth.

POST /v1/events

{
  "phone": "+2348012345678",
  "name": "Ada Obi",
  "type": "cart.abandoned",
  "data": { "items": "3", "value": "48500" },
  "idempotencyKey": "cart-8821-abandoned"
}
FieldTypeNotes
typerequiredstringWhat happened. Free text, and it must match a workflow's trigger exactly — see below.
phonestringE.164. One of phone, customerId or externalId is required.
customerIduuidOur id, if you stored it.
externalIdstringYour id. Must already exist here — unlike a phone number, it will not enrol anyone.
namestringUsed only if this call creates the customer. Ignored otherwise.
dataobjectWhatever the workflow needs. At most 50 top-level fields.
idempotencyKeystringSend one. The next section is about why.
optInobject{ "evidence": "Checkout checkbox" } when the customer agreed to WhatsApp messages on your site. Recorded before the event, as your declaration with that evidence. Needed for marketing templates such as cart reminders; never lifts an opt-out. See Connect your website.

Event types are free text, and that cuts both ways

Nothing validates type against a list, because your vocabulary is yours. The cost is that a typo is indistinguishable from a deliberate new type: order_created sent to a workspace whose workflow waits for order.created stores perfectly, returns 201, and does nothing — forever, with no error anywhere.

That is what matchedWorkflows in the response is for. Nonzero means something is waiting on this type. Zero means nothing is — which is correct and expected for an event you are recording for its own sake, and a bug the rest of the time.

Check matchedWorkflows on your first integration test

It is the single cheapest way to catch a mismatch, and it costs nothing to assert in the code that sends the event. The alternative is finding out in month three when somebody asks why the follow-ups stopped — except they never started.

Seeing what happened, without asking anybody

Activity → Events lists everything received, newest first, and each row says what it started: the automations that ran, or why none did. Four different answers, because they need four different fixes — nothing is listening for that event name, an automation is listening but is switched off, one is switched on but was never published, or one was ready and still did not run for this customer.

Events pushed in also say which system sent them, so a shop running Shopify, the WooCommerce plugin and a script of its own can tell the three apart. The plugin is recognised by its User-Agent; everything else reads as your own code.

Deliveries that were not accepted

A refused delivery used to leave no trace here at all: the sender got a 400 and the business saw an empty list, which reads as “no orders yet” rather than “we turned eleven of them away”. Activity → Not accepted is that list — a customer we could not find, an order Shopify sent with no phone number on it, a field that failed validation — each with the exact sentence the sender was given.

Identical problems within ten minutes are shown once, each workspace keeps its most recent few hundred for a month, and nothing replays from there: by the time anyone reads it, the cart was either bought or forgotten. Fix it where it was sent from and send the next one.

Idempotency keys

Send the same key twice and the second call records nothing and returns the first event, with recorded: false. The key is scoped to your workspace and to the API source, so it cannot collide with an event that arrived some other way.

This matters more than it first appears. Consider a retry:

POST /v1/events  →  network timeout
                     (did it arrive? you cannot tell)
POST /v1/events  →  retry

Without a key, that is two events. If a workflow triggers on the type, it is also two follow-up messages to the same customer — visible, annoying, and billed twice by Meta. With a key, the retry is free and the customer notices nothing.

Make the key describe the occurrence, not the attempt. A UUID generated fresh per request defeats the entire mechanism, because the retry carries a different one. Derive it from something stable in your own data:

order-8821-created        an order can only be created once
cart-8821-abandoned-3     the third time this cart was abandoned
quote-8821-followup-2026-09-13

If recorded comes back false on every call, your key is not varying when it should. If it is never false even on retries, it is varying when it should not.

Time

An event is stamped when it is recorded. Workflows measure their delays from that moment, so an event sent from a nightly batch describes something that happened during the day but is treated as happening at the time of the batch — a “follow up in two hours” workflow will fire two hours after the batch ran, not two hours after the customer went quiet. Send events as they happen where you can.

Next

Channels →

Connect the WhatsApp number that events will eventually send from.