Delivering through Joinotify Cloud
The plugin delivers messages through a channel. There are two WhatsApp channels, and they coexist on the same site:
| Channel | How it connects | Message types |
|---|---|---|
| WhatsApp (Cloud API) | Joinotify Cloud token | text, media, audio and template |
| WhatsApp (Evolution) | QR Code, over the legacy relay | text, media and audio |
The Cloud channel talks to the Joinotify Cloud API — the same one any external integration uses — and behind it sits Meta's official Cloud API. It is the recommended path: the number is official, the send is signed by Meta and it does not depend on a live WhatsApp Web session on the server.
Setting it up
Go to Joinotify → Settings → General, in the WhatsApp Cloud API card.
| Field | What it does |
|---|---|
| Message transport | Automatic, Cloud API (official) or Evolution (legacy) |
| API token | your Joinotify Cloud token (sk_live_…) |
| Phone number ID | the phone_number_id used as the origin |
| WhatsApp Business Account ID | the waba_id that owns the templates |
In Automatic mode — the default — the plugin uses the Cloud API whenever a token is available and falls back to Evolution when there is none. The other two values force a specific transport, which is handy for testing or for holding back the migration.
When your license already has a Joinotify Cloud number provisioned, the token, the
phone_number_id and the waba_id arrive with the license. The fields above are manual
overrides: fill them in only to point the site at another number or another account. Left
blank, whatever came with the license wins.
Where to find each identifier in the Joinotify Cloud dashboard is described in Quickstart.
The 24-hour window changes what you can send
This is the difference you notice day to day. On the legacy relay, free-form text went out at any time. On the official Cloud API, outside the 24-hour window since the customer's last message, only an approved template is delivered — free-form text is rejected by Meta.
In practice:
- Workflows replying to an ongoing conversation keep working exactly as before.
- Workflows that start the conversation — "your order is out for delivery", "review your purchase 7 days later" — fall outside the window and need a template.
The full concept, with template categories and what each one costs, is in The 24-hour window and Templates.
The builder offers WhatsApp: text message and WhatsApp: media message. Template sending exists on the channel but not yet as a workflow node — for now it is reachable from code (see below) or straight through the Cloud API.
Sending a template from code
Sending goes through the notification layer, which resolves the active channel:
$result = joinotify_dispatch_notification( array(
'channel' => 'whatsapp_cloud',
'type' => 'template',
'sender' => joinotify_get_first_sender(),
'receiver' => '5541987111527',
'meta' => array(
'template_name' => 'order_shipped',
'language' => 'en_US',
'components' => array(
array(
'type' => 'body',
'parameters' => array(
array( 'type' => 'text', 'text' => '1042' ),
),
),
),
),
) );
if ( ! $result->is_success() ) {
error_log( 'Template send failed: ' . $result->error );
}
The shape of components is the same as the API's — see
Sending messages.
How the plugin picks the origin number
The phone_number_id is resolved from the workflow's sender. When that number has no
phone_number_id of its own mapped, the account default applies — the Phone number ID
setting, or the value provisioned with the license.
Errors and retries
A failed send is not thrown away: the message goes to the processing queue (Joinotify → Processing queue) and is retried. Configuration errors — invalid token, missing template, closed window — are not retried, because insisting would not change the outcome; they are recorded in the history and in the logs.
The most common production error is the closed window, recorded as
window_closed_requires_template. The full code table is in Errors.
Pointing at another environment
To test against something other than production, define the constant in wp-config.php:
define( 'JOINOTIFY_CLOUD_API_BASE_URL', 'https://api.example.com' );
Without the constant, the plugin talks to https://api.joinotify.com.