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 49 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
- Messages customers send, and messages typed on the phone or a linked device, each logged as a note on the contact with the matching phone number. A new contact is created only if the connection allows it.
- sent from WavesFlo
- Logged only through two further switches that come with the Sales module: inbox replies, naming who sent them, and campaigns and automatic replies. Each needs “Log messages sent from the WhatsApp app” on.
- 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.
apps
Zapier and Make
The WavesFlo apps: an instant trigger for every event, registered when the Zap or scenario is switched on, and WavesFlo actions in any step.
node
n8n
Install n8n-nodes-wavesflo: the WavesFlo node offers every action in a workflow, and its trigger starts one on any published event.
mcp server
AI apps over MCP
Claude, Cursor, VS Code, Windsurf, Gemini CLI and the n8n AI Agent call WavesFlo as tools, limited to what their key’s scopes allow.
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. Install the WavesFlo node for n8n, n8n-nodes-wavesflo, from n8n’s community nodes, and a workflow can log each new message in your CRM. Power Automate connects through WavesFlo’s custom connector.
- 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.
| Resource | Endpoints | Scopes |
|---|---|---|
| Conversations |
|
|
| Tickets |
|
|
| Calls |
|
|
| Pipelines |
|
|
| Opportunities |
|
|
| Companies |
|
|
| Tasks |
|
|
Conversations, tickets, calls and sales
28 endpoints for conversations, tickets, calls, pipelines, opportunities, companies and tasks, under 7 scopes of their own: for a helpdesk sync, a CRM sync or a BI export.
- A key is an integration: it sees every record in its workspace, and history records its changes as made through the API.
- Never to the customer: assigning, closing or noting a conversation through the API sends nothing on WhatsApp.
- No silent overwrites: send the version you read with an edit, and a stale one is refused with the fields that changed.
Webhooks
49 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, calls, groups, consent, campaigns, accounts, support, sales, companies and tasks and inbox.
- message.received
- message.sent
- message.ack
- message.reaction
- message.media_stored
- poll.vote
- call.received
- call.ended
- call.placed
- group.participant_joined
- group.participant_left
- group.settings_changed
- group.membership_request
- group.member_added
- group.member_removed
- group.updated
- group.job_completed
- consent.withdrawn
- consent.granted
- campaign.started
- campaign.completed
- campaign.failed
- campaign.approval_required
- account.connected
- account.disconnected
- account.qr_required
- account.send_blocked
- account.restricted
- ticket.created
- ticket.updated
- ticket.resolved
- ticket.sla_breached
- message.flagged
- message.unflagged
- opportunity.created
- opportunity.updated
- opportunity.stage_changed
- opportunity.won
- opportunity.lost
- opportunity.deleted
- company.created
- company.updated
- company.deleted
- task.created
- task.updated
- task.completed
- task.due
- conversation.assigned
- conversation.updated
- 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 typed on a connected number's phone leaves that number; sends made from WavesFlo itself are not included |
| 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 |
| call.received | Calls | A call rings a connected number, and again when someone picks it up |
| call.ended | Calls | A call ends in either direction, answered or not |
| call.placed | Calls | A call placed from the inbox starts ringing, or is answered |
| 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 |
| group.member_added | Groups | A bulk group job adds someone to a group, including one it creates |
| group.member_removed | Groups | A bulk group job removes someone from a group |
| group.updated | Groups | A bulk group job changes a group's name, description or settings |
| group.job_completed | Groups | A bulk group job finishes, done or failed |
| 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 |
| account.restricted | Accounts | A number appears restricted by WhatsApp and stays offline until checked |
| ticket.created | Support | A ticket is opened |
| ticket.updated | Support | A ticket's status, priority, assignee or other fields change |
| ticket.resolved | Support | A ticket is resolved or closed |
| ticket.sla_breached | Support | A ticket misses its first-reply or resolution target |
| message.flagged | Support | A message from someone outside the team starts waiting for a reply |
| message.unflagged | Support | A waiting message is answered, cleared by hand or put on a ticket |
| opportunity.created | Sales | An opportunity is opened in the pipeline |
| opportunity.updated | Sales | An opportunity's details change, or a deleted one is restored |
| opportunity.stage_changed | Sales | An opportunity moves to another stage, won and lost included |
| opportunity.won | Sales | An opportunity moves into a won stage |
| opportunity.lost | Sales | An opportunity moves into a lost stage |
| opportunity.deleted | Sales | An opportunity is deleted; a restore arrives as opportunity.updated |
| company.created | Companies and tasks | A company is added |
| company.updated | Companies and tasks | A company's details change |
| company.deleted | Companies and tasks | A company is deleted |
| task.created | Companies and tasks | A task, follow-up, logged call, meeting or note is added |
| task.updated | Companies and tasks | A task of any kind changes, or a finished one is reopened |
| task.completed | Companies and tasks | A task of any kind but a note is marked done |
| task.due | Companies and tasks | A reminder on an open task comes due, once per reminder |
| conversation.assigned | Inbox | A conversation is assigned, reassigned or unassigned |
| conversation.updated | Inbox | A conversation's archive, pin, mute, team or done state changes |
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 included in the plan.
Can the WavesFlo API read and update tickets, calls and sales records?
Yes. Beside messaging, the WavesFlo API has 28 endpoints for conversations, tickets, calls, pipelines, opportunities, companies and tasks, under 7 scopes of their own. An integration can list and read conversations with their messages and notes, assign them, mark them done and add internal notes; open, update and comment on tickets; read the call register and its totals; and create and edit opportunities, companies and tasks, including moving an opportunity to won or lost. A key acts for the workspace rather than a person, so it sees every record in it, and history records its changes as made through the API. Deletes, call recordings and exports are not part of the API.
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 49 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); calls (call.received, call.ended, call.placed); groups (group.participant_joined, group.participant_left, group.settings_changed, group.membership_request, group.member_added, group.member_removed, group.updated, group.job_completed); 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, account.restricted); support (ticket.created, ticket.updated, ticket.resolved, ticket.sla_breached, message.flagged, message.unflagged); sales (opportunity.created, opportunity.updated, opportunity.stage_changed, opportunity.won, opportunity.lost, opportunity.deleted); companies and tasks (company.created, company.updated, company.deleted, task.created, task.updated, task.completed, task.due); inbox (conversation.assigned, conversation.updated). 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 keeps a sales pipeline on its own WhatsApp conversations, with no quotas and no lead scoring; your CRM stays the record for everything else.
Can I automate WavesFlo without writing code?
Yes. Inside WavesFlo, Workflows (beta) joins a trigger to steps, with templates, Build with AI and HTTP request and webhook steps. Zapier, Make and n8n also have WavesFlo apps (n8n's is the n8n-nodes-wavesflo package), so a workflow can send messages, work tickets and deals, or start from any of the 49 published webhook events; Power Automate connects through a custom connector. Approval requests, campaign results and number alerts can go to Slack or Microsoft Teams, and AI apps such as Claude or Cursor connect over MCP.
Get a key and send your first message.
A walkthrough on two of your own numbers, with your rollout questions answered.
