Connecting WordPress and WooCommerce
With the Joinotify plugin connected to your account, a WordPress site can do more than send messages: it syncs with the platform. The store's customers become contacts, along with what the store knows about them — how many orders they placed, how much they spent, when they last bought. And what happens on the site — an order paid, a sign-up, an abandoned cart, a form submitted — reaches your flows as a site event, through the Site event trigger.
The difference from the webhook trigger: there is no URL to paste and no
field to map. The site already says who the customer is and sends each event with a name —
wc.order.paid — that any flow in the account can listen to. What each event carries is in the
site event catalog.
Turning the sync on
The site must be connected to Joinotify. The connection alone sends nothing beyond what sending messages needs: syncing is off by default, and nothing leaves the site before you switch it on.
- In WordPress, go to Joinotify → Settings → Applications and click Configure on the Joinotify Cloud sync card.
- Read the text at the top of the window: it says exactly what the site will send.
- Choose what to sync (table below), switch the card on and save.
| Option | Default | What it does |
|---|---|---|
| WooCommerce orders and customers | on | Order and subscription events, and each customer with their order count, total spent and last purchase |
| WordPress users | on | Sign-ups and profile updates |
| Form submissions | off | WPForms and Elementor Pro submissions — with every field of the form |
| Abandoned carts | on | Captured contacts and abandoned, recovered or lost carts from Flexify Checkout |
| Send the items of each order | on | Products, quantities and categories of each order |
| Send billing and shipping addresses | off | The full address. City, state and country are always sent |
| Tag for contacts from this site | empty | The tag every contact from the site gets. Empty means the site's name |
| Ask customers for marketing consent | off | The consent checkbox at checkout, in the sign-up forms and in My account (see below) |
| Consent checkbox text | empty | The checkbox text. Say what the person will receive, and on which channel |
Besides the customers, the site reports its own address, its name, the Joinotify version, which store, form and checkout plugins are active, and a made-up sample of each event — that is how the flow editor shows the fields before the first order arrives.
Nothing is sent during checkout. Each event is written to a queue on the site itself and sent in batches, in the background, right after. If Joinotify does not answer, the event waits in the queue and is sent again later. Switching the card off stops sending at once; what already arrived stays in your account.
What the platform does with each contact
The sync adds and never deletes:
- A contact is found first by who they are on the site (the WordPress user, the store customer) and then by phone. A customer who changes numbers stays one contact; if the new number already belongs to another contact, nothing is merged and the clash is recorded on the timeline.
- Without a phone, no contact is created — the platform is WhatsApp. The event is recorded as No contact.
- Name and e-mail only fill what is empty: what someone typed in the panel wins.
- Tags only come in. Every person gets the site's source tag, which lets you build audiences and campaigns with that store's customers only.
- The store fields — Orders, Total spent, Average order value, First purchase, Last purchase, Last order status, City, State, Registered on the site, Role on the site and, with subscriptions, Subscription status and Next payment — are created by the platform on the first sync and take the snapshot the site computes: they replace the previous value.
- A new contact counts toward your plan's contact limit. Above it, the contact is not created and the event is recorded as Contact limit. Existing contacts carry on as usual.
On the contact's page, the source shows as Connected site, and the On the site section shows who the person is on each connected site — WordPress user, WooCommerce customer, Buyer without an account or Form or cart lead — and when the data is from.
Marketing consent
With Ask customers for marketing consent on, an unticked checkbox shows:
- at checkout — both the classic and the block checkout (WooCommerce 8.9 or newer);
- in the WordPress and WooCommerce sign-up forms;
- in My account → Account details.
Whoever already agreed does not see the checkbox again at checkout. Whoever ticks it has the checkbox text, where and when stored on the order or on the user, as evidence, and that person's next event carries the opt-in to Joinotify. Whoever unticks the preference in My account opts out on the platform — and that opt-out always applies.
Two rules protect your base:
- Consent only moves up from unknown: a checkbox ticked on an old order never undoes an opt-out, and never takes anyone off the suppression list.
- The way back works too: an opt-out made on Joinotify — by keyword, in the panel, by a flow — is reported to the site, and the person's preference shows unticked there. An opt-in made on Joinotify comes back as well.
Without the checkbox, contacts arrive with unknown consent: transactional messages remain possible, and marketing campaigns follow the rules in Contacts and consent.
Sending the customers you already have
Events only reach whoever buys or signs up from now on. For your first campaign to reach the customers the store already has, send them once:
- In the sync window, under Existing customers, click Send existing customers.
- Check the estimate: how many users have a phone number, and how many orders were placed without an account.
- Click Send them. It runs in the background — you can close the page. Stop interrupts it, and whoever is already queued is still sent.
Each person goes once: users first, then buyers without an account, by e-mail (someone with an account goes as the account). No flow starts for anyone who arrives this way. People without a phone number are left out and counted, and your plan's contact limit is respected. At the end, the window shows how many were sent, how many are waiting, how many were given up, and what the platform did with some of them — no phone number, invalid phone number, over the plan's limit, kept opted out.
Monitoring from WordPress
The top of the window shows whether the sync is on (Sync on), off (Sync off) or paused (Sync paused), the last delivery and the last error, and three counters: what is waiting (Waiting), what was sent in the last 7 days (Sent (7 days)) and what was given up (Given up).
A send that fails is retried at growing intervals — from 1 minute up to 1 hour. After 12 attempts, or when the platform refuses the event for what it is, it is given up and listed with its reason. Once you fix the problem, Send again sends it again; Discard deletes it. Given-up items stay on the site for 30 days.
The sync pauses when retrying would not help:
| Reason | What to do |
|---|---|
| The site's key was revoked or cannot send this data | Connect the site again; the sync resumes on its own |
| The site was removed from your account | Connect the site again |
| The site is at another address than its key's | If it is a staging copy, leave it paused. If the site moved, connect it again |
While paused, the sync keeps queueing events on the site, and nothing is lost. Try again retries right away. A site paused in the panel (below) does not pause the plugin: events wait and are offered again every 15 minutes, until you resume it.
Staging copies
A copy of the site at another address — staging, a clone, a test environment — that took the production key along is refused by Joinotify and stays paused: test orders never reach real customers. In the panel, the site shows that a copy tried to use its key. To test syncing on a copy, connect the copy on its own: it becomes a separate site under Integrations.
In the panel: Integrations
The Integrations page lists the connected sites, with each one's active integrations, its last event, and its events and problems over the last 24 hours. From it you can:
- Pause a site: what it sends is refused until you Resume it — the plugin keeps it and sends it later;
- Disconnect: the site's key is revoked right away. Contacts and history stay, and connecting the same site again picks up where it left off;
- See events: every event the site sent, its contact and its outcome.
| Status | What it means |
|---|---|
| Queued | It arrived and is about to be processed |
| Processed | It was handed to the flows that listen to this event |
| Handed to a wait | No flow started, but a run in Wait for event moved on |
| No flow listens | No active flow uses this event |
| No contact | The event carried no contact that could be found or created |
| Contact limit | The contact would be new, and the account is at its plan's limit |
| Account blocked | The account has no access to sending |
| Error | Something failed while processing it |
Problems only narrows the list to what needs attention. Each event's detail shows the data, the contact sent by the site and what each flow did with it — Started, Filtered out, Repeated, In cooldown, Other site, No next step. Process again hands the event to the flows again; Process again even if it was already handled skips the trigger's Act once per. An event's data is kept for 30 days, or for your plan's message retention period if that is shorter.
Building a flow with the Site event trigger
- In the editor, add a trigger and choose Site event.
- Under Event, pick the event from the list, grouped by source. It shows the events your connected sites reported; for an event from your own code, use Custom event.
- Under Sites, tick which sites the event counts from. None ticked: any site in the account.
- The Event sample shows the fields — click a field to use it in messages.
- Connect the trigger to the first step, publish and activate the flow.
There is no contact to map: it comes in the event itself. The trigger has the same options as the
webhook — Filter, Keep on the contact, Contact timeline, With the contact's other
automations (the default is Run alongside) and Minimum interval per contact — plus one
more: Act once per. With order.id, the same order does not start the flow twice within 24
hours, even on wc.order.status_changed, which goes out on every status change. The same event
delivered twice by the site never starts the flow twice.
The event's data is in {{trigger.…}} — {{trigger.order.number}},
{{trigger.order.total:money}}, {{trigger.links.payment_url}} — and the flow also has
{{trigger.$event.name}} and {{trigger.$site.name}}. The paths of every event are in the
catalog.
Waiting for a site event
The Wait for event step holds the run until a connected site sends an event that matches it. Under Wait for, choose A site event and fill in:
- Site event — what is awaited, such as
wc.order.paid; - This run's value — a variable of this run, such as
{{trigger.order.id}}; - Where the same value is in the awaited event — the path in the event to come, such as
order.id; - the maximum wait, which is required (up to 30 days).
Values are compared as text, ignoring case. If the event arrives, the run moves on through the
Arrived output; if time runs out, through Did not arrive. With Keep as a variable, the
event that arrived is kept in {{flow.<name>.…}}.
Recipes
Pending Pix
Whoever generated a Pix and did not pay gets a reminder 30 minutes later — and whoever paid does not.
- Site event trigger:
wc.order.created. Filter:order.statusequalspendingandorder.payment_methodequals the id of your Pix payment method (check it in the Event sample). - Wait for event:
wc.order.paid, value{{trigger.order.id}}, pathorder.id, up to 30 minutes. - Through Did not arrive, send the reminder with
{{trigger.order.total:money}}and the link{{trigger.links.payment_url}}. Through Arrived, end — or say thanks.
The customer has most likely not written to the store in the last 24 hours: the reminder has to be a template.
Abandoned cart
- Site event trigger:
fcrc.cart.abandoned, with Act once percart.id. - A template with the link back to the cart,
{{trigger.cart.recovery_url}}. - Wait for event:
fcrc.cart.recovered, value{{trigger.cart.id}}, pathcart.id, up to 1 day. - Through Did not arrive, the second reminder. Through Arrived, end.
Review after the purchase
- Site event trigger:
wc.order.completed, with Act once perorder.id. - Wait 7 days.
- A template asking for a review of
{{trigger.order.line_items[0].name}}, with the link{{trigger.links.review_urls[0]}}.
A condition on customer.is_first_order tells the first purchase from the next ones — for a
different thank-you to someone who just met the store.
WordPress flows and platform flows
The flows of the plugin's builder, in WordPress, and the platform's flows run independently: the same order can trigger both, and nothing warns you or stops it. Avoiding repeated messages is the responsibility of whoever runs the store — when you move an automation to the platform, deactivate the WordPress one (and vice versa).
Custom events
With the sync on, your code can send events of its own through joinotify_track():
joinotify_track(
'custom.quote.requested',
array( 'quote' => array( 'id' => 991, 'total' => '1290.00' ) ),
array(
'ref' => array( 'kind' => 'wp_user', 'id' => (string) $user_id ),
'phone' => '+5511987654321',
'first_name' => 'Ana',
)
);
- The name starts with
custom.and has one to three parts of lowercase letters, digits and_. - The second argument is the event's
data: in the flow,{{trigger.quote.total}}. - The third says who the event is about and needs a
ref; without one, the event is recorded but reaches no contact. Forwc_guestandlead, theidis a hash, never the e-mail in the clear. - The function returns the event id, or
falsewith the sync off or a name outsidecustom..
For the editor to offer the event with its fields, describe it with a made-up sample in the
Joinotify/Cloud_Sync/Catalog filter:
add_filter( 'Joinotify/Cloud_Sync/Catalog', function( $entries ) {
$entries[] = array(
'name' => 'custom.quote.requested',
'schemaVersion' => 1,
'sample' => array( 'quote' => array( 'id' => 1, 'total' => '100.00' ) ),
);
return $entries;
} );
The Send webhook action in the WordPress builder
The plugin's flow builder has a Send webhook action that sends data to any URL — n8n, Zapier, Make, your ERP or the webhook trigger of a platform flow:
- the body is the trigger's data, in the same shape as site events (order, customer, links, user, cart), or a JSON of your own, with the plugin's placeholders;
- method
POST,PUTorPATCH, and headers asName: value, one per line; - with a secret, each call carries
X-Joinotify-TimestampandX-Joinotify-Signature-256(sha256=and the HMAC-SHA256 oftimestamp.body), so the receiver can check where it came from.
Only public addresses are accepted. The call goes out when the flow reaches the action, waits up to 10 seconds and does not follow redirects; a failure goes to the plugin's log. The action does not depend on the sync.
Privacy
WordPress's privacy tools (Tools → Export Personal Data and Tools → Erase Personal Data) cover the sync:
- export lists the person's marketing consent and what the site's queue holds about their e-mail;
- erase removes the consent and the queue rows, and asks Joinotify to erase the contacts this site linked to the person — found by who they are on the site, never by phone. The contact is erased whole, as in the panel's erasure. If Joinotify cannot erase it right away, WordPress says so: erase it from the panel or run the request again.
Switching the sync off does not erase what already arrived: the contacts stay in the account, where you can erase them.
Through the API
Events arrive through POST /site-events, and the initial load's customers through
POST /contacts/sync, always with the site's key. The envelope is described in the
event catalog, and the routes in the API reference.