MCP is live.Set upAsk the community
OmniDimension

WhatsApp API

Send and receive WhatsApp messages, manage templates and opt-outs, and run broadcasts from your own code.

The WhatsApp API does everything the dashboard does: send a message, read a conversation, hand one over to a person, manage templates and opt-outs, and run a broadcast. The agent answers incoming messages by default, and your code follows along by polling.

Base URL https://omnidim.io/api/v1  ·  All routes live under /api/v1/whatsapp.

Before you start

  • An API key. Copy it from the dashboard. See Authentication.
  • A WhatsApp number connected, in the dashboard or over the API with your Meta access token and WhatsApp Business Account id. See Connect a number below, or Connect a number in the dashboard.
  • An approved template for any chat you start yourself, because the 24-hour window is closed until the customer writes to you. See Templates.
  • An agent on the number if you want replies answered. See Agent replies.

To see the whole API at a glance, open the WhatsApp reference.

How a conversation flows

  1. 1

    Set up the number

    Connect WhatsApp, approve a template, and attach the agent that answers.
  2. 2

    Send from your code

    POST /whatsapp/messages: free text inside the 24-hour window, or an approved template any time.
  3. 3

    The agent answers incoming messages

    Replies go out on their own. There is nothing to build for this.
  4. 4

    Your integration follows the chat

    New messages

    Poll GET /whatsapp/messages with after, or conversations with updated_since.

    A person steps in

    POST take_over to stop the agent, chat, then hand_back.

Your step Done for you

Steps 1 and 2 are yours. The agent does step 3 on its own.

The routes, by task

TaskStart here
Connect your own Meta numberConnect numbers
Find your number and what it can sendList numbers
Send a text, template, image or documentSend a message
Read a chat, and poll it for new messagesList messages
The list of chats on a numberList conversations
A person takes over, then hands backTake over
Upload an image or document to sendUpload media
Templates, and syncing them from MetaList templates
Block someone, or lift a blockList opt-outs
A broadcast to a listCreate a campaign
Turn calling on or offGet calling

Lists come back as { "data": [...], "has_more": true, "next_cursor": "..." }. Pass limit (1 to 100, default 20) and hand next_cursor back until it is null.

Connect a number

Connect numbers from your own WhatsApp Business Account with a Meta access token that never expires and has both WhatsApp permissions. A system user token does. Meta credentials shows where to find the token and the account id.

Preview the numbers

API reference: Preview numbers

curl -X POST "https://backend.omnidim.io/api/v1/whatsapp/numbers/preview" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"waba_id": "YOUR_WABA_ID", "access_token": "YOUR_META_TOKEN"}'

Each number comes back with its phone_number_id and its health. A token that cannot connect is refused here, with a code that says why (token_expires, token_missing_permissions or token_invalid).

Connect the ones you want

API reference: Connect numbers

curl -X POST "https://backend.omnidim.io/api/v1/whatsapp/numbers" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "waba_id": "YOUR_WABA_ID",
    "access_token": "YOUR_META_TOKEN",
    "phone_number_ids": ["PHONE_NUMBER_ID"],
    "app_secret": "YOUR_META_APP_SECRET"
  }'

The answer lists each connected number with its id, the id every other route takes. receiving says whether this request turned replies on. Send app_secret to turn them on. Find it in your Meta app under App settings > Basic. Numbers that could not connect are in failed, with the reason.

To change the token or the app secret later, connect the same number again with the new value. It updates the number.

Send a message

Find your number

API reference: List numbers

curl "https://backend.omnidim.io/api/v1/whatsapp/numbers" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "data": [
    {
      "id": 412,
      "phone_number": "+15551234567",
      "provider": "meta",
      "name": "Support",
      "status": "active",
      "quality_rating": "GREEN",
      "messaging_limit": "TIER_10K",
      "capabilities": {
        "types": ["text", "template", "image", "document"],
        "reply_to": true,
        "window_enforced": true,
        "delivery_status": "push",
        "template_sync": true
      }
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Keep the id. capabilities.types says what this number can send. A status other than active means the number cannot send right now.

Send a text, inside the window

API reference: Send a message

curl -X POST "https://backend.omnidim.io/api/v1/whatsapp/messages" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2d9e-0b7a-4c1e-9a55-3e2f8b1d7c40" \
  -d '{
    "phone_number_id": 412,
    "to": "+15557654321",
    "type": "text",
    "text": "Your appointment is confirmed for Friday at 10 am."
  }'

The send comes back as 201 with the message, ready to look up again:

{
  "id": 98123,
  "conversation_id": "412-15557654321",
  "sender": "person",
  "type": "text",
  "text": "Your appointment is confirmed for Friday at 10 am.",
  "status": "pending",
  "timestamp": "2026-10-18T09:04:11+00:00",
  "timeline": [{ "status": "accepted", "at": "2026-10-18T09:04:11+00:00" }]
}

A free-form send needs an open 24-hour window, which the customer opens by messaging you. Outside it, the send is refused with 409 window_closed and the message asks for a template. For a new chat, or any send after a day of silence, use a template.

Or start the chat with a template

Find an approved template, then send it by its id. A template works whether or not the window is open, and it can be the first message in a conversation.

curl "https://backend.omnidim.io/api/v1/whatsapp/numbers/412/templates?status=approved" \
  -H "Authorization: Bearer YOUR_API_KEY"

Each template lists the fields a send must fill:

{
  "data": [
    {
      "id": 3676,
      "name": "appointment_reminder",
      "language": "en_US",
      "category": "UTILITY",
      "status": "approved",
      "body_preview": "Hi {{1}}, your appointment is on {{2}}.",
      "fields": [
        { "key": "1", "label": "Customer name", "kind": "text", "required": true, "example": "Demo User" },
        { "key": "2", "label": "Appointment time", "kind": "text", "required": true, "example": "Friday, 10 am" }
      ],
      "buttons": []
    }
  ],
  "has_more": false,
  "next_cursor": null
}
curl -X POST "https://backend.omnidim.io/api/v1/whatsapp/messages" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9a2b7c1e-4d6f-4a80-9c33-5e1f2d8b6a07" \
  -d '{
    "phone_number_id": 412,
    "to": "+15557654321",
    "type": "template",
    "template": { "id": 3676, "params": { "1": "Rahul", "2": "Friday, 10 am" } }
  }'

A template you have not synced yet will not be in the list. Run Sync templates after you create or edit one in Meta.

Send a file

API reference: Upload media

Upload first, then send by the returned media_id. Uploading costs nothing; only the send is charged.

curl -X POST "https://backend.omnidim.io/api/v1/whatsapp/media" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "phone_number_id=412" \
  -F "type=document" \
  -F "file=@invoice.pdf"
{
  "media_id": "kQ8v2mZ0pLc4xR7tN1sW3yB6dF9hJ5aE",
  "type": "document",
  "mime": "application/pdf",
  "filename": "invoice.pdf",
  "size": 48213,
  "expires_at": "2026-10-19T09:04:11+00:00"
}

Then send it with type image or document and a media object. The media_id works for 24 hours, for that number and type only.

Read and follow messages

There are no event webhooks, so you poll. Two ways, depending on what you need:

  • Follow one chat. Call List messages with conversation_id and after set to the newest message id you already have. You get only the messages newer than it, newest first.
  • Watch the whole inbox. Call List conversations with updated_since set to the time of your last call. You get every conversation with a change since then, including new messages, takes over and opt-outs.
curl "https://backend.omnidim.io/api/v1/whatsapp/messages?conversation_id=412-15557654321&after=98123" \
  -H "Authorization: Bearer YOUR_API_KEY"

To read a message's delivery, call Get a message. Its timeline walks through accepted, sent, delivered and read, and a failed message carries a failure with a reason and whether a retry is safe.

Take over and hand back

The agent owns a conversation by default and answers every incoming message. An API send does not take it over: send a message and the agent keeps replying.

To answer as a person, take the conversation over. The agent goes quiet, and customer messages are still stored and shown but not answered.

curl -X POST "https://backend.omnidim.io/api/v1/whatsapp/conversations/412-15557654321/take_over" \
  -H "Authorization: Bearer YOUR_API_KEY"

Hand back when the person is done and the agent resumes on the next message. You can also set take_over: true on a send to do both in one call.

Broadcasts

A campaign sends one approved template to a list. It is always created as a draft:

  1. Create a campaign with a name, a phone_number_id and an approved template_id.
  2. Add contacts, one object up to 500 at once. Each needs a phone_number. Any other key you send can fill a template parameter through variable_mapping.
  3. Launch it with a new Idempotency-Key. Every pending contact is queued once.

Launch checks the plan's monthly broadcast allowance and your wallet balance. If either runs short, launch answers 402. You can pause, resume and cancel a running campaign, and read per-contact status with List contacts.

Scheduling is not offered over the API. In the dashboard the same broadcasts are described at Broadcasts.

Opt-outs

A customer stops your messages by replying STOP, or by turning marketing off in WhatsApp. Both are listed under List opt-outs.

Block a customer yourself with their number, and lift it when they ask to come back. A block the customer made themselves cannot be lifted over the API: the request answers 409 customer_opt_out, because only the customer can opt back in by messaging you.

Calling

WhatsApp calling lets a customer call the number, and the agent answers. It is off by default, only on Meta Cloud numbers, and must be included in your plan. Read the state and the checks with Get calling, then Turn it on once every blocking check passes. See WhatsApp calls for the product side.

Errors

Every error is { "error", "error_description", "code", "request_id" }, and X-Request-Id is on every response. code is the specific reason to branch on.

StatusWhat it means
400A bad parameter or body. code says which, such as invalid_parameter, invalid_limit or token_expires.
401The API key is missing or wrong.
402Not enough wallet balance, or the monthly broadcast allowance is used up.
403Your plan or role cannot do this.
404No such number, conversation, message, template, campaign or opt-out in your workspace.
409A state conflict: window_closed, opted_out, not_supported, invalid_transition or a reused idempotency key.
429Too many template syncs. Retry-After says how long to wait.
502WhatsApp could not be reached. Nothing was sent or charged.
503WhatsApp is not connected for the number, or is briefly unavailable.

On this page