Starting a flow by webhook
The Webhook trigger gives a flow its own URL. Any platform that can send a webhook — your store, a form, a CRM, a course platform — calls that URL with its own data, in its own format, and the flow starts. Every field of what arrived can be used in messages, templates, conditions and contact fields.
The difference from the API trigger: there, your system calls Joinotify with a key and in Joinotify's format. Here there is no key and no format to follow — you paste a URL into a setting of the platform and choose in the editor where each piece of data comes from.
Preparing the flow
- In the editor, add a trigger and choose Webhook. The URL shows right away, before publishing — it is what you paste into the platform.
- Under Platform, choose WordPress / WooCommerce or Any platform.
- Click Wait for event and send a test from the platform (or paste a sample under Paste JSON). What arrives becomes a tree: from then on you pick fields by clicking instead of typing paths.
- Under Contact, say where the phone is — and, if you want, the name and the e-mail.
- Connect the trigger to the first step, publish and activate the flow.
The URL looks like https://api.joinotify.com/hooks/whk_… and it is the trigger's password:
whoever has the URL can start the flow. If it leaks, Generate a new URL changes the address —
with the option of keeping the old one working for 24 hours, to leave time to paste the new one.
WooCommerce
- In WordPress, open WooCommerce → Settings → Advanced → Webhooks and click Add webhook.
- Status: Active. Topic: Order created — or Order updated, to follow payment and shipping.
- Delivery URL: the trigger's URL.
- Secret: on the trigger, click Turn on the WooCommerce signature and paste the generated secret here.
- Save. With Wait for event on, place a test order in the store to capture a sample.
With WooCommerce chosen as the platform and nothing else set, the contact comes from the order's
billing block: billing.phone, billing.first_name, billing.last_name and billing.email. A
phone without a country code, like (11) 98765-4321, is read as belonging to the country chosen
on the trigger (Brazil by default).
The test WooCommerce sends when the webhook is saved (webhook_id=…) is answered without
becoming an event.
The Order updated topic fires on every change to the order. Use the trigger's Filter to
start only on what matters — status equal to processing, for example — and let the same order
with the same status count only once (the default, see Repeated events).
What the platform can send
- JSON (
application/json) — an object; a list arrives asitems. - Form (
application/x-www-form-urlencoded), including nested fields the way PHP writes them:customer[phone]=…becomescustomer.phone. - Text, which arrives as
text. - The URL's query string is in
$query, and the platform's headers (such asX-WC-Webhook-Topic) in$headers— never the ones that authenticate.
The body can be up to 256 kB. POST, PUT and PATCH are accepted; a GET on the URL
answers 200, for platforms that test the address before saving it.
Signature
On top of the URL, the platform can prove it was the one sending. On the trigger, under Signature:
| Type | What Joinotify checks |
|---|---|
| The URL only | Nothing but the address (default) |
| WooCommerce | X-WC-Webhook-Signature: HMAC-SHA256 of the body, in Base64, with the webhook's secret |
| HMAC-SHA256 | The header you choose, in hex or Base64, with an optional prefix (sha256=) |
| Fixed header | The header you choose holds exactly the agreed value |
The signature is checked over the bytes that arrived, before the body is read. A call with
the wrong signature gets 401 and does not become an event.
Responses
The platform only gets an error when the call is wrong:
| Status | When |
|---|---|
202 | The event was accepted — { "ok": true, "event_id": "…" } |
404 | The URL does not exist (or was replaced and the old one's grace period is over) |
401 | The signature does not match |
400 | The JSON is not valid |
413 | The body is over 256 kB |
415 | A format Joinotify does not read (multipart/form-data) |
429 | Too many calls — up to 20 per second per URL and 60 per account |
Everything else — a paused flow, an account without access, an order without a phone, a repeated
event — is 202 and is recorded under Received events. On purpose: a platform that gets
errors retries and, after a few failures, disables the webhook, and nobody finds out why.
Received events
In the editor, Received events lists every call and what happened to it:
| Status | What it means |
|---|---|
| Started the flow | A run started for the contact |
| Filtered out | The event did not pass the trigger's filter |
| Repeated | The same event had already arrived |
| No phone | No valid phone at the chosen path |
| Unknown contact | The trigger is set not to create contacts, and the phone belongs to none |
| Flow inactive | The flow is a draft or paused |
| Trigger not published | The published version does not have this trigger |
| Trigger with no next step | The contact was updated, but nothing was sent |
| Within the interval | It arrived within the minimum interval per contact |
| Delivered to a waiting run | A run on Wait for event went on with it |
| Account blocked | The account has no access to sending |
Each event's detail shows the data (with CPF, CNPJ and card numbers masked until you ask), the headers and the run it started, and has Run again. An event's data is kept for 30 days, or your plan's message retention, when it is shorter. After that only its status stays in the log. On the flows list, a flow whose webhook had three or more events that went nowhere in the last 24 hours shows a warning.
Repeated events
| Option | Counts as the same event |
|---|---|
| Automatic — WooCommerce | The same order with the same status, for 30 days: editing the order does not confirm it again |
| Automatic — others | An idempotency header (Idempotency-Key, X-Webhook-Id, X-Event-Id, X-Delivery-Id), for 24 hours; without one, the same body within 5 minutes |
| By a field | The same value in the chosen field, for 24 hours |
| By a header | The same value in the chosen header, for 24 hours |
| Never | Every event starts the flow |
A filtered event does not count as seen: the next one that passes the filter starts normally.
Alongside the contact's other automations
By default, a run started by a webhook runs alongside whatever the contact already has going on: an order does not interrupt the survey or the automated service the person is answering. When the contact writes, the reply goes to the run that is waiting for text — a question, a Wait for reply —, not to a template waiting for a button tap.
On the trigger you can switch to Replace only this flow's previous run or Replace any automation in progress (like the conversation triggers), and set a minimum interval per contact.
Keeping data on the contact, and the timeline
- Keep on the contact copies event fields to the contact's fields before the flow starts — the last order's value, the tax id, the purchase date. They are later useful to segment campaigns and in other flows. The name and e-mail the platform sends only fill in what is empty.
- Timeline writes a line in the contact's history, which the team sees in the inbox:
Order {{trigger.number}} — {{trigger.total:money}}.
Waiting for another event
The Wait for event step holds a run until a webhook trigger — of this flow or another — receives an event with the same value in a field. It is what makes a cart recovery stop when the order is paid:
- A flow starts on the abandoned-cart event and sends the first reminder.
- Wait for event: the paid-order trigger, this run's value
{{trigger.cart_token}}, the pathcart_tokenin the awaited event, for up to 1 day. - Through the Arrived output, say thanks; through Did not arrive, send the second reminder.
The event that arrived is in {{flow.<name>.…}}, if you give it a name on the step.
Testing
Test, in the editor, can carry the captured sample: the run starts from the webhook trigger on your WhatsApp, with the variables filled in as they would be for the customer.
Next: Variables and data in flows.