WhatsApp automation API: Connect WavesFlo to your CRM, or build on the API.
HubSpot and Zoho CRM are built in, and every plan includes a REST API and 20 published, signed webhook events for everything else.
Yes, two in stock, and instalments work on most cards. Shall I hold one for you till Saturday?
Hi, is the 55-inch OLED in stock at Store 214? Can I pay in instalments?
Integrations
What WavesFlo connects to, and how.
HubSpot and Zoho CRM, built in.
An Admin connects either one from the WavesFlo dashboard, by signing in to the CRM.
- switching on
- Nothing is sent to the CRM until a workspace Admin switches syncing on, and every switch starts off.
- one-to-one chats
- Incoming and outgoing, each logged as a note on the contact with the matching phone number. A new contact is created only if the connection allows it.
- opt-outs
- Opt-outs and opt-ins are logged as a note on the same contact.
- never logged
- Group, status and channel messages. Media is not copied.
- contact import
- CRM contacts with an international phone number, on demand or every six hours, without duplicates. An import never records consent.
- disconnecting
- Revokes WavesFlo’s access at the CRM.
alerts
Slack and Microsoft Teams
Approval requests, campaign results and number alerts, posted to the channel you choose.
campaign source
Google Sheets
A sheet can feed a campaign, one personalised message per row. Update the sheet and the next run picks up the change.
rest api and webhooks
n8n
Connect n8n through the REST API and signed webhooks: send from a workflow, or start one from a published event.
rest api and webhooks
Power Automate
Call the REST API from a flow, or start one from a signed webhook event. Power Apps and Logic Apps connect the same way.
Any other CRM, through n8n
n8n has its own node for each of these. Connect n8n to WavesFlo through the REST API and signed webhooks, and a workflow can log each new message in your CRM. Power Automate connects the same way.
- Salesforce
- Microsoft Dynamics 365
- Pipedrive
- Freshworks CRM
- Odoo
- monday.com
- Keap
- Copper
- Salesmate
- Agile CRM
For whoever builds it
The full API is on every plan, including the cheapest.
Get a key
Issued per workspace, scoped to it, and revocable without affecting anyone else.
Call the API
REST over HTTPS, JSON both ways, with the key in a header.
Subscribe
Point a URL at the events you want, and verify the signature on each delivery.
REST API
Send a message in one call.
Sends and other create actions accept an Idempotency-Key: a retry with the same key within 24 hours returns the first result instead of repeating the action.
curl -X POST "$WAVESFLO_HOST/api/public/v1/messages" \ -H "x-api-key: $WAVESFLO_KEY" \ -H "Idempotency-Key: msg-9931" \ -H "Content-Type: application/json" \ -d '{"to":"919900000000@s.whatsapp.net","body":"Hello from WavesFlo."}'{ "success": true, "data": { "waMessageId": "3EB0A1B2C3D4E5F60718", "accountId": "session_1", "rerouted": false, "trackedLinks": [] }}On every send
- The same limits: opt-outs, daily caps, cooldowns and quiet hours are enforced on the server, so an API send meets the limits a campaign from the dashboard does.
- A reason for every refusal: a send the limits hold back is refused with its reason, and says whether waiting will help.
- The number that sent it: the response names it, since a direct message leaves from the number that owns the contact.
Webhooks
20 published, signed webhook events.
Each delivery is signed over its timestamp and raw body together, so a replayed or edited payload fails verification.
import { createHmac, timingSafeEqual } from "node:crypto" function verify(rawBody, headers, secret) { const ts = headers["x-wavesflo-timestamp"] const sig = String(headers["x-wavesflo-signature"] ?? "").replace("sha256=", "") if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false const digest = createHmac("sha256", secret) .update(`${ts}.${rawBody}`).digest() const sent = Buffer.from(sig, "hex") return sent.length === digest.length && timingSafeEqual(sent, digest)}// Sign over the raw body, before any JSON parsing, and reject stale timestamps.The published events
Each destination subscribes to the categories it needs: messaging, engagement, groups, consent, campaigns and accounts.
- message.received
- message.sent
- message.ack
- message.reaction
- message.media_stored
- poll.vote
- group.participant_joined
- group.participant_left
- group.settings_changed
- group.membership_request
- consent.withdrawn
- consent.granted
- campaign.started
- campaign.completed
- campaign.failed
- campaign.approval_required
- account.connected
- account.disconnected
- account.qr_required
- account.send_blocked
- delivery
- At least once, retried with backoff until the attempt cap.
- retry scope
- Only the endpoints that failed, never the ones that already succeeded.
- event identifier
- Stable across retries, so deduplicating is a lookup.
- ordering
- Not guaranteed. Order by the event timestamp, not by arrival.
More delivery settings
- destinations
- Multiple URLs, each subscribed to its own event categories.
- method
- Chosen per destination.
- signature
- HMAC-SHA256 over the timestamp and the raw body together, not the body alone.
- attempt log
- One row per attempt, so a four-failure history is readable.
- auto-disable
- After 20 consecutive failures, with the reason recorded.
- redirects
- Not followed. A 3xx is not success: nothing received the payload.
- address guard
- Loopback, private ranges and cloud metadata refused at registration.
- header protection
- Your auth headers cannot overwrite the header that authenticates us.
A per-endpoint serial queue would guarantee order and would let one slow consumer stall every event for every other customer. Each payload carries the time it occurred; order on that.
What each event fires on
| Event | Category | Fires when |
|---|---|---|
| message.received | Messaging | An inbound message arrives on a connected number |
| message.sent | Messaging | A message leaves a connected number, including one typed on the phone |
| message.ack | Messaging | An acknowledgement state changes for a message we sent |
| message.reaction | Messaging | Someone reacts to a message, or removes their reaction |
| message.media_stored | Messaging | Inbound media finishes downloading and is stored |
| poll.vote | Engagement | Someone votes in a poll, or changes their vote |
| group.participant_joined | Groups | Someone joins a group a connected number is in |
| group.participant_left | Groups | Someone leaves or is removed from such a group |
| group.settings_changed | Groups | A group's subject, description or admin-only settings change |
| group.membership_request | Groups | Someone asks to join a group with approval enabled |
| consent.withdrawn | Consent | Someone asks to stop hearing from you, in any scope |
| consent.granted | Consent | Consent is recorded or restored in a scope |
| campaign.started | Campaigns | A campaign begins sending, including after an approval |
| campaign.completed | Campaigns | A campaign finishes its queue |
| campaign.failed | Campaigns | A campaign stops without completing |
| campaign.approval_required | Campaigns | A campaign is waiting on a human decision |
| account.connected | Accounts | A number finishes linking and can send |
| account.disconnected | Accounts | A number drops off, within minutes of the missed heartbeat |
| account.qr_required | Accounts | A number needs re-linking before it can send again |
| account.send_blocked | Accounts | A send is refused by the safety gate, per recipient |
Read from the backend’s own event contract on .
Questions
What developers ask before they build.
How do I start building on the WavesFlo API?
Get a key, call the API, then subscribe to events. WavesFlo issues API keys per workspace, scoped to it and revocable without affecting anyone else. The API is REST over HTTPS, with JSON both ways and an Idempotency-Key header accepted on sends and other create actions. For webhooks, point a URL at the events you want and verify the signature on each delivery. The full API is on every plan, including the cheapest.
Is it safe to retry a WavesFlo API request?
Yes, if the request carries an Idempotency-Key header. Sends and other create actions accept an Idempotency-Key: a retry with the same key within 24 hours returns the first result instead of repeating the action. Reusing a key with a different body is refused, so a timed-out send is safe to retry with the same key.
Which webhook events does WavesFlo send?
WavesFlo publishes 20 signed webhook events, in categories that each destination URL subscribes to separately: messaging (message.received, message.sent, message.ack, message.reaction, message.media_stored); engagement (poll.vote); groups (group.participant_joined, group.participant_left, group.settings_changed, group.membership_request); consent (consent.withdrawn, consent.granted); campaigns (campaign.started, campaign.completed, campaign.failed, campaign.approval_required); accounts (account.connected, account.disconnected, account.qr_required, account.send_blocked). The message.received and message.sent events carry the id of the connected number the message arrived on or left from.
How are WavesFlo webhooks delivered and retried?
WavesFlo delivers webhooks at least once. A failed delivery is retried with backoff until the attempt cap, and only to the endpoints that failed, never the ones that already succeeded; every attempt is logged. Order is not guaranteed, so sort by the timestamp each payload carries, and deduplicate on the event identifier, which stays the same across retries. A destination is disabled after 20 consecutive failures, with the reason recorded.
Does WavesFlo integrate with HubSpot, Zoho CRM or Salesforce?
HubSpot and Zoho CRM connect natively. Once a workspace Admin switches syncing on, WavesFlo logs one-to-one WhatsApp messages and opt-out or opt-in changes as notes on the matching CRM contact; group, status and channel messages never reach the CRM. WavesFlo can also import CRM contacts with a usable international phone number, on demand or every six hours, and an import never records consent. Salesforce and other CRMs connect through n8n or Power Automate, using the REST API and the signed message.received webhook. WavesFlo is not itself a CRM.
Can I automate WavesFlo without writing code?
Yes. Connect n8n through the REST API and signed webhooks, so a workflow can send messages, manage groups and contacts, or start from any of the 20 published webhook events; Power Automate connects the same way. Approval requests, campaign results and number alerts can go to Slack or Microsoft Teams, and a Google Sheet can feed a campaign.
Get a key and send your first message.
A walkthrough on two of your own numbers, with your rollout questions answered.