Skip to content
WavesFlo

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.

Illustration

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.

  • HubSpot
  • Zoho 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
Every integration, with setup for each

For whoever builds it

The full API is on every plan, including the cheapest.

  1. Get a key

    Issued per workspace, scoped to it, and revocable without affecting anyone else.

  2. Call the API

    REST over HTTPS, JSON both ways, with the key in a header.

  3. 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."}'
Response
{ "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.
The Work and Sales endpoints, under /api/public/v1
ResourceEndpointsScopes
Conversations
  • GET /conversations
  • GET /conversations/{id}
  • GET /conversations/{id}/messages
  • PATCH /conversations/{id}
  • POST /conversations/{id}/notes
  • inbox:read
  • inbox:write
Tickets
  • GET /tickets
  • GET /tickets/{id}
  • POST /tickets
  • PATCH /tickets/{id}
  • POST /tickets/{id}/comments
  • tickets:read
  • tickets:write
Calls
  • GET /calls
  • GET /calls/summary
  • GET /calls/{id}
  • calls:read
Pipelines
  • GET /sales/pipelines
  • sales:read
Opportunities
  • GET /sales/opportunities
  • GET /sales/opportunities/{id}
  • POST /sales/opportunities
  • PATCH /sales/opportunities/{id}
  • POST /sales/opportunities/{id}/stage
  • sales:read
  • sales:write
Companies
  • GET /sales/companies
  • GET /sales/companies/{id}
  • POST /sales/companies
  • PATCH /sales/companies/{id}
  • sales:read
  • sales:write
Tasks
  • GET /sales/tasks
  • GET /sales/tasks/{id}
  • POST /sales/tasks
  • PATCH /sales/tasks/{id}
  • POST /sales/tasks/{id}/complete
  • sales:read
  • sales:write

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
What each published webhook event fires on
EventCategoryFires when
message.receivedMessagingAn inbound message arrives on a connected number
message.sentMessagingA message typed on a connected number's phone leaves that number; sends made from WavesFlo itself are not included
message.ackMessagingAn acknowledgement state changes for a message we sent
message.reactionMessagingSomeone reacts to a message, or removes their reaction
message.media_storedMessagingInbound media finishes downloading and is stored
poll.voteEngagementSomeone votes in a poll, or changes their vote
call.receivedCallsA call rings a connected number, and again when someone picks it up
call.endedCallsA call ends in either direction, answered or not
call.placedCallsA call placed from the inbox starts ringing, or is answered
group.participant_joinedGroupsSomeone joins a group a connected number is in
group.participant_leftGroupsSomeone leaves or is removed from such a group
group.settings_changedGroupsA group's subject, description or admin-only settings change
group.membership_requestGroupsSomeone asks to join a group with approval enabled
group.member_addedGroupsA bulk group job adds someone to a group, including one it creates
group.member_removedGroupsA bulk group job removes someone from a group
group.updatedGroupsA bulk group job changes a group's name, description or settings
group.job_completedGroupsA bulk group job finishes, done or failed
consent.withdrawnConsentSomeone asks to stop hearing from you, in any scope
consent.grantedConsentConsent is recorded or restored in a scope
campaign.startedCampaignsA campaign begins sending, including after an approval
campaign.completedCampaignsA campaign finishes its queue
campaign.failedCampaignsA campaign stops without completing
campaign.approval_requiredCampaignsA campaign is waiting on a human decision
account.connectedAccountsA number finishes linking and can send
account.disconnectedAccountsA number drops off, within minutes of the missed heartbeat
account.qr_requiredAccountsA number needs re-linking before it can send again
account.send_blockedAccountsA send is refused by the safety gate, per recipient
account.restrictedAccountsA number appears restricted by WhatsApp and stays offline until checked
ticket.createdSupportA ticket is opened
ticket.updatedSupportA ticket's status, priority, assignee or other fields change
ticket.resolvedSupportA ticket is resolved or closed
ticket.sla_breachedSupportA ticket misses its first-reply or resolution target
message.flaggedSupportA message from someone outside the team starts waiting for a reply
message.unflaggedSupportA waiting message is answered, cleared by hand or put on a ticket
opportunity.createdSalesAn opportunity is opened in the pipeline
opportunity.updatedSalesAn opportunity's details change, or a deleted one is restored
opportunity.stage_changedSalesAn opportunity moves to another stage, won and lost included
opportunity.wonSalesAn opportunity moves into a won stage
opportunity.lostSalesAn opportunity moves into a lost stage
opportunity.deletedSalesAn opportunity is deleted; a restore arrives as opportunity.updated
company.createdCompanies and tasksA company is added
company.updatedCompanies and tasksA company's details change
company.deletedCompanies and tasksA company is deleted
task.createdCompanies and tasksA task, follow-up, logged call, meeting or note is added
task.updatedCompanies and tasksA task of any kind changes, or a finished one is reopened
task.completedCompanies and tasksA task of any kind but a note is marked done
task.dueCompanies and tasksA reminder on an open task comes due, once per reminder
conversation.assignedInboxA conversation is assigned, reassigned or unassigned
conversation.updatedInboxA 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.