Womnibot API

Read and update contacts, follow conversations, send WhatsApp messages and get notified the moment something happens, from any language that can make an HTTPS request.

Base URL: https://womnibot.com/api/v1

Quickstart

1. Create a key. In the dashboard open Developers, choose a name and the permissions it needs, and confirm your password. Copy the key: it's shown once.

2. Check it works.

bash
export WOMNIBOT_API_KEY="wmb_live_…"

curl https://womnibot.com/api/v1/me -H "Authorization: Bearer $WOMNIBOT_API_KEY"

3. Send your first message. Templates can go to any opted-in contact. Text replies work within 24 hours of the customer's last message.

bash
curl -X POST https://womnibot.com/api/v1/messages \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"to":"+972501234567","type":"template","template":{"name":"order_shipped","language":"en","components":[{"type":"body","parameters":[{"type":"text","text":"#4821"},{"type":"text","text":"https://track.example/4821"}]}]}}'
javascript
const res = await fetch("https://womnibot.com/api/v1/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.WOMNIBOT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({ to: "+972501234567", type: "text", text: { body: "Hi from the API 👋" } }),
});
const message = await res.json();
if (!res.ok) throw new Error(`${message.error.code}: ${message.error.message}`);

Authentication

Send your key as a bearer token on every request: Authorization: Bearer wmb_live_…. Keys belong to one workspace and can only use the permissions (scopes) chosen when they were created. A key never inherits the access of the person who made it.

ScopeAllows
contacts:readRead contacts
contacts:writeCreate and update contacts
conversations:readRead conversations and their messages
messages:sendSend WhatsApp messages (text inside the 24h window, templates any time)
templates:readRead message templates
broadcasts:readRead broadcasts and their statistics
broadcasts:writeCreate and send broadcasts (reserved: no v1 endpoints yet)
webhooks:manageCreate, list and delete webhook endpoints
numbers:manageConnect and disconnect WhatsApp numbers
flows:writeList chatbot flows and create or edit drafts (a person publishes them)

Keep keys on your server, never in browser or mobile code. If a key leaks, revoke it in Developers; it stops working immediately. Only workspace owners and admins can create keys, and creating one requires re-entering a password.

WhatsApp rules

WhatsApp enforces two rules the API applies for you:

  • The 24-hour window. You can send free-form text only within 24 hours of the customer's last message. Check a conversation's reply_window_ends_at; after it, sending text returns window_closed.
  • Templates and consent. Outside the window, send a Meta-approved template. Templates are only sent to contacts with opted_in: true, so set it when you have the person's consent; otherwise you get not_opted_in.

Errors

Errors use standard HTTP status codes and a consistent body. Include the correlation_id (also in the X-Correlation-Id response header) when you contact support.

json
{
  "error": {
    "code": "window_closed",
    "message": "The 24-hour reply window is closed; send an approved template instead",
    "correlation_id": "5d1c2f0e-…"
  }
}
400validation_failedThe request is malformed; details lists each field problem
401unauthorizedMissing, invalid or revoked key
403insufficient_scopeThe key doesn't have the scope this endpoint needs
404not_foundNo such resource in this workspace
409conflict / channel_not_connectedAlready exists, or no WhatsApp number is connected
422window_closed / not_opted_in / idempotency_conflictA rule prevents this action
429rate_limitedToo many requests; wait Retry-After seconds
502provider_errorWhatsApp rejected the message; see message
500internal_errorSomething broke on our side; safe to retry with the same Idempotency-Key

Pagination

List endpoints return newest items first, up to limit (default 25, max 100). When there are more, pass the returned next_cursor as cursor to get the next page. next_cursor: null means you've reached the end.

javascript
let cursor = null;
do {
  const url = new URL("https://womnibot.com/api/v1/contacts");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);
  const page = await (await fetch(url, { headers })).json();
  for (const contact of page.data) handle(contact);
  cursor = page.next_cursor;
} while (cursor);

Idempotency

Networks fail. To retry safely, send an Idempotency-Key header (any unique string, such as a UUID) on POST requests. If we've already processed that key, you get the original response back with an Idempotent-Replayed: true header, and nothing happens twice. Reusing a key with a different request returns idempotency_conflict.

Rate limits

Each key can make 120 requests per minute. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets). Over the limit you get 429 rate_limited with a Retry-After header. WhatsApp also limits how many new conversations your number can start per day, based on its messaging tier.

Webhooks

Register an HTTPS endpoint in Developers (or with the API) and we'll POST events to it as they happen. Respond with any 2xx within 10 seconds. Otherwise we retry after 1 minute, 5 minutes, 30 minutes, 2, 6, 12 and 24 hours, then give up. Delivery is at-least-once, so use the event id to ignore duplicates.

json
{
  "id": "evt_3f2a9c…",
  "type": "message.received",
  "schema_version": "2026-09-28",
  "created_at": "2026-09-28T10:04:40.512Z",
  "workspace_id": "ws_EWhJ…",
  "data": {
    "message": {
      "object": "message",
      "id": "m_77c0…",
      "conversation_id": "v_3c20…",
      "direction": "inbound",
      "sender": "customer",
      "type": "text",
      "text": "Where is my order?"
    }
  }
}
contact.createdA contact was created (first inbound message, API or dashboard)
conversation.openedA new conversation started
message.receivedA customer sent a message
message.sentA message was sent to a customer (dashboard or API)
message.delivery_updatedA sent message changed status: sent, delivered, read or failed

Verifying signatures

Every delivery has a Womnibot-Signature header like t=1790590000,v1=5257a8…. Compute an HMAC-SHA256 of {t}.{raw request body} using your endpoint's signing secret, compare it to v1 in constant time, and reject timestamps older than 5 minutes. Always verify against the raw body, before parsing JSON.

javascript (Node.js / Express)
import crypto from "node:crypto";

app.post("/webhooks/womnibot", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("Womnibot-Signature") ?? "";
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto
    .createHmac("sha256", process.env.WOMNIBOT_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`)
    .digest("hex");

  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  const valid = v1 && v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
  if (!fresh || !valid) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  // Deduplicate on event.id, then handle event.type…
  res.sendStatus(200);
});
python (Flask)
import hmac, hashlib, time, os
from flask import Flask, request, abort

app = Flask(__name__)

@app.post("/webhooks/womnibot")
def womnibot():
    parts = dict(p.split("=", 1) for p in request.headers.get("Womnibot-Signature", "").split(","))
    raw = request.get_data()
    expected = hmac.new(os.environ["WOMNIBOT_WEBHOOK_SECRET"].encode(),
                        f"{parts.get('t')}.".encode() + raw, hashlib.sha256).hexdigest()
    if abs(time.time() - int(parts.get("t", 0))) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(400)
    event = request.get_json()
    # Deduplicate on event["id"], then handle event["type"]…
    return "", 200

API reference

Account

About the API key in use

Get the current API key

get/me

Works with any valid key.

Request
curl "https://womnibot.com/api/v1/me" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "api_key",
  "id": "k_1f0e…",
  "name": "Shopify sync",
  "key_prefix": "wmb_live_Ab12Cd",
  "scopes": [
    "contacts:read",
    "messages:send"
  ],
  "workspace": {
    "id": "ws_EWhJ…",
    "name": "AliBuy"
  },
  "created_at": "2026-09-28T09:00:00.000Z"
}

Numbers

Your connected WhatsApp business numbers

List your WhatsApp numbers

get/numbers

Use a number's id as from_number_id when starting a conversation. The first-connected number is the default.

Requires the conversations:read scope.

Request
curl "https://womnibot.com/api/v1/numbers" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "object": "number",
      "id": "n_4a1b…",
      "phone": "+972 52-842-3112",
      "verified_name": "Alibuy",
      "quality_rating": "green",
      "messaging_limit_tier": "TIER_1K",
      "default": true,
      "created_at": "2026-09-28T08:00:00.000Z"
    }
  ],
  "next_cursor": null
}

Connect a WhatsApp number

post/numbers

Connects (or reconnects) a WhatsApp Cloud API number. The details are checked with Meta before anything is stored; the token is stored encrypted. See /llms.txt for how to get the values from Meta.

Requires the numbers:manage scope.

Request
curl -X POST "https://womnibot.com/api/v1/numbers" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

Disconnect a WhatsApp number

delete/numbers/{id}

Conversations are kept. Nothing changes at Meta.

Requires the numbers:manage scope.

ParameterDescription
id
path · required
Number ID
Request
curl -X DELETE "https://womnibot.com/api/v1/numbers/ID" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

Contacts

People you talk to on WhatsApp

List contacts

get/contacts

Newest first.

Requires the contacts:read scope.

ParameterDescription
limit
query
Page size
cursor
query
The `next_cursor` from the previous page
tag
query
Only contacts with this tag
q
query
Search by name or phone number
Request
curl "https://womnibot.com/api/v1/contacts?limit=2&tag=vip" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "object": "contact",
      "id": "c_7d3f…",
      "wa_id": "972501234567",
      "phone": "+972501234567",
      "name": "Dana Levi",
      "tags": [
        "vip"
      ],
      "opted_in": true,
      "opted_in_at": "2026-09-28T10:02:11.000Z",
      "source": "API",
      "last_seen_at": null,
      "created_at": "2026-09-28T10:02:11.000Z"
    }
  ],
  "next_cursor": "eyJ0Ijoi…"
}

Create a contact

post/contacts

Set opted_in: true only when you have the person's consent to receive WhatsApp messages from you.

Requires the contacts:write scope.

Request
curl -X POST "https://womnibot.com/api/v1/contacts" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"phone":"+972501234567","name":"Dana Levi","tags":["vip"],"opted_in":true}'
Response
{
  "object": "contact",
  "id": "c_7d3f…",
  "wa_id": "972501234567",
  "phone": "+972501234567",
  "name": "Dana Levi",
  "tags": [
    "vip"
  ],
  "opted_in": true,
  "opted_in_at": "2026-09-28T10:02:11.000Z",
  "source": "API",
  "last_seen_at": null,
  "created_at": "2026-09-28T10:02:11.000Z"
}

Get a contact

get/contacts/{id}

Requires the contacts:read scope.

ParameterDescription
id
path · required
Contact ID
Request
curl "https://womnibot.com/api/v1/contacts/ID" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "contact",
  "id": "c_7d3f…",
  "wa_id": "972501234567",
  "phone": "+972501234567",
  "name": "Dana Levi",
  "tags": [
    "vip"
  ],
  "opted_in": true,
  "opted_in_at": "2026-09-28T10:02:11.000Z",
  "source": "API",
  "last_seen_at": null,
  "created_at": "2026-09-28T10:02:11.000Z"
}

Update a contact

patch/contacts/{id}

Requires the contacts:write scope.

ParameterDescription
id
path · required
Contact ID
Request
curl -X PATCH "https://womnibot.com/api/v1/contacts/ID" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tags":["vip","wholesale"]}'
Response
{
  "object": "contact",
  "id": "c_7d3f…",
  "wa_id": "972501234567",
  "phone": "+972501234567",
  "name": "Dana Levi",
  "tags": [
    "vip",
    "wholesale"
  ],
  "opted_in": true,
  "opted_in_at": "2026-09-28T10:02:11.000Z",
  "source": "API",
  "last_seen_at": null,
  "created_at": "2026-09-28T10:02:11.000Z"
}

Conversations

One conversation per contact, with its messages

List conversations

get/conversations

Requires the conversations:read scope.

ParameterDescription
limit
query
Page size
cursor
query
The `next_cursor` from the previous page
status
query
One of: open, resolved.
contact_id
query
unassigned
query
Only conversations with no assignee
Request
curl "https://womnibot.com/api/v1/conversations?status=open&unassigned=true" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "object": "conversation",
      "id": "v_3c20…",
      "contact_id": "c_7d3f…",
      "status": "open",
      "assignee_id": null,
      "unread_count": 2,
      "last_message_at": "2026-09-28T10:04:40.000Z",
      "last_inbound_at": "2026-09-28T10:04:40.000Z",
      "reply_window_ends_at": "2026-09-29T10:04:40.000Z",
      "created_at": "2026-09-27T16:20:00.000Z"
    }
  ],
  "next_cursor": null
}

Get a conversation

get/conversations/{id}

Requires the conversations:read scope.

ParameterDescription
id
path · required
Conversation ID
Request
curl "https://womnibot.com/api/v1/conversations/ID" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "conversation",
  "id": "v_3c20…",
  "contact_id": "c_7d3f…",
  "status": "open",
  "assignee_id": null,
  "unread_count": 0,
  "last_message_at": "2026-09-28T10:05:00.000Z",
  "last_inbound_at": "2026-09-28T10:04:40.000Z",
  "reply_window_ends_at": "2026-09-29T10:04:40.000Z",
  "created_at": "2026-09-27T16:20:00.000Z"
}

List messages in a conversation

get/conversations/{id}/messages

Newest first. Default page size 50.

Requires the conversations:read scope.

ParameterDescription
id
path · required
Conversation ID
limit
query
Page size
cursor
query
The `next_cursor` from the previous page
Request
curl "https://womnibot.com/api/v1/conversations/ID/messages" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "object": "message",
      "id": "m_91ab…",
      "conversation_id": "v_3c20…",
      "direction": "outbound",
      "sender": "agent",
      "type": "text",
      "text": "Your order has shipped 🚚",
      "status": "sent",
      "error": null,
      "whatsapp_message_id": "wamid.HBgM…",
      "created_at": "2026-09-28T10:05:00.000Z"
    },
    {
      "object": "message",
      "id": "m_77c0…",
      "conversation_id": "v_3c20…",
      "direction": "inbound",
      "sender": "customer",
      "type": "text",
      "text": "Where is my order?",
      "status": null,
      "error": null,
      "whatsapp_message_id": "wamid.HBgN…",
      "created_at": "2026-09-28T10:05:00.000Z"
    }
  ],
  "next_cursor": null
}

Messages

Send WhatsApp messages

Send a message

post/messages

Send to an existing conversation (conversation_id) or to an existing contact's phone number (to; the conversation is created if needed). Create new contacts first with POST /contacts.

Text can only be sent within 24 hours of the customer's last message (WhatsApp's customer-service window); otherwise you get window_closed. Templates can be sent any time, but only to contacts with opted_in: true.

Requires the messages:send scope.

Template example
json
{
  "to": "+972501234567",
  "type": "template",
  "template": {
    "name": "order_shipped",
    "language": "en",
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "#4821"
          },
          {
            "type": "text",
            "text": "https://track.example/4821"
          }
        ]
      }
    ]
  }
}
Request
curl -X POST "https://womnibot.com/api/v1/messages" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"to":"+972501234567","type":"text","text":{"body":"Your order has shipped 🚚"}}'
Response
{
  "object": "message",
  "id": "m_91ab…",
  "conversation_id": "v_3c20…",
  "direction": "outbound",
  "sender": "agent",
  "type": "text",
  "text": "Your order has shipped 🚚",
  "status": "sent",
  "error": null,
  "whatsapp_message_id": "wamid.HBgM…",
  "created_at": "2026-09-28T10:05:00.000Z"
}

Templates

Meta-approved message templates

List message templates

get/templates

Requires the templates:read scope.

ParameterDescription
limit
query
Page size
cursor
query
The `next_cursor` from the previous page
status
query
One of: approved, pending, rejected, paused, disabled.
Request
curl "https://womnibot.com/api/v1/templates?status=approved" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "object": "template",
      "id": "t_55aa…",
      "name": "order_shipped",
      "language": "en",
      "category": "utility",
      "status": "approved",
      "components": [
        {
          "type": "BODY",
          "text": "Your order {{1}} has shipped! Track it here: {{2}}"
        }
      ],
      "updated_at": "2026-09-12T08:00:00.000Z"
    }
  ],
  "next_cursor": null
}

Sync templates from Meta

post/templates/sync

Refreshes templates for every connected WhatsApp Business Account. Also runs automatically every 15 minutes.

Requires the templates:read scope.

Request
curl -X POST "https://womnibot.com/api/v1/templates/sync" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

Broadcasts

Bulk template sends and their statistics

List broadcasts

get/broadcasts

Requires the broadcasts:read scope.

ParameterDescription
limit
query
Page size
cursor
query
The `next_cursor` from the previous page
Request
curl "https://womnibot.com/api/v1/broadcasts" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "object": "broadcast",
      "id": "b_0c1d…",
      "name": "Autumn sale",
      "template_id": "t_55aa…",
      "status": "sent",
      "scheduled_at": null,
      "sent_at": "2026-09-24T09:00:00.000Z",
      "stats": {
        "recipients": 12480,
        "delivered": 12102,
        "read": 9870,
        "replied": 1432
      },
      "created_at": "2026-09-24T08:55:00.000Z"
    }
  ],
  "next_cursor": null
}

Webhooks

Receive signed events at your own URL

List webhook endpoints

get/webhook-endpoints

Requires the webhooks:manage scope.

Request
curl "https://womnibot.com/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "object": "webhook_endpoint",
      "id": "w_9e8f…",
      "url": "https://example.com/webhooks/womnibot",
      "events": [
        "message.received"
      ],
      "enabled": true,
      "created_at": "2026-09-28T09:10:00.000Z"
    }
  ],
  "next_cursor": null
}

Create a webhook endpoint

post/webhook-endpoints

The response includes the endpoint's signing secret. It is shown only once. The URL must be public HTTPS.

Requires the webhooks:manage scope.

Request
curl -X POST "https://womnibot.com/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"url":"https://example.com/webhooks/womnibot","events":["message.received","message.delivery_updated"]}'
Response
{
  "object": "webhook_endpoint",
  "id": "w_9e8f…",
  "url": "https://example.com/webhooks/womnibot",
  "events": [
    "message.received",
    "message.delivery_updated"
  ],
  "enabled": true,
  "created_at": "2026-09-28T09:10:00.000Z",
  "secret": "whsec_Qm9v…"
}

Delete a webhook endpoint

delete/webhook-endpoints/{id}

Requires the webhooks:manage scope.

ParameterDescription
id
path · required
Webhook endpoint ID
Request
curl -X DELETE "https://womnibot.com/api/v1/webhook-endpoints/ID" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"
Response
{
  "object": "webhook_endpoint",
  "id": "w_9e8f…",
  "deleted": true
}

Flows

Chatbot flow drafts (a person publishes them)

List chatbot flows

get/flows

Requires the flows:write scope.

Request
curl "https://womnibot.com/api/v1/flows" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

Create a chatbot flow draft

post/flows

Creates a draft. It never runs until a person reviews and publishes it in the dashboard at review_url; the API cannot publish.

Requires the flows:write scope.

Request
curl -X POST "https://womnibot.com/api/v1/flows" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

Get a chatbot flow

get/flows/{id}

Requires the flows:write scope.

ParameterDescription
id
path · required
Flow ID
Request
curl "https://womnibot.com/api/v1/flows/ID" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

Update a flow's draft

patch/flows/{id}

Replaces the draft and/or renames the flow. A published version keeps running unchanged until a person publishes again.

Requires the flows:write scope.

ParameterDescription
id
path · required
Flow ID
Request
curl -X PATCH "https://womnibot.com/api/v1/flows/ID" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

AI agents

Let an AI assistant connect with a person's approval (see /llms.txt)

Start an AI agent session

post/agent-sessions

No key needed. Returns an approval_url for your person, who signs up or signs in and approves the requested scopes. Then poll /agent-sessions/token. MCP clients can use OAuth at /mcp instead.

Request
curl -X POST "https://womnibot.com/api/v1/agent-sessions" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

Collect the API key after approval

post/agent-sessions/token

Poll every 5 seconds. status is pending, approved (with api_key, returned exactly once), denied, expired or claimed.

Request
curl -X POST "https://womnibot.com/api/v1/agent-sessions/token" \
  -H "Authorization: Bearer $WOMNIBOT_API_KEY"

Machine-readable contract: /api/v1/openapi.json (OpenAPI 3.1). Import it into Postman or generate a client in your language.