Skip to content

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.

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
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.

  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.

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
What each published webhook event fires on
EventCategoryFires when
message.receivedMessagingAn inbound message arrives on a connected number
message.sentMessagingA message leaves a connected number, including one typed on the phone
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
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
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

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.