Skip to content

Messaging gateway API

One API for every messaging channel

Send WhatsApp Business, SMS, Telegram, Messenger, Instagram and TikTok messages through a single REST endpoint. Prepaid, billed per outbound message, with delivery webhooks and a test mode.

From
$0.0003
per outbound message
On signup
100
free messages
Commitment
None
prepaid, no contract
curl https://api.omnimessage.co/v1/messages \
  -H "Authorization: Bearer om_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{
    "channel": "ch_7Hq2mN5vB8cX1zL0pK3j",
    "to": "+971501234567",
    "type": "text",
    "text": { "body": "Your order #1042 has shipped." },
    "reference": "order-1042"
  }'
202 Accepted
{
  "id": "msg_2b1Xw9aQ3rT8yU0pL4kZ",
  "object": "message",
  "mode": "live",
  "channel_id": "ch_7Hq2mN5vB8cX1zL0pK3j",
  "channel_type": "whatsapp",
  "direction": "outbound",
  "to": "+971501234567",
  "from": "+971800123456",
  "type": "text",
  "content": {
    "text": { "body": "Your order #1042 has shipped." }
  },
  "status": "queued",
  "error": null,
  "reference": "order-1042",
  "metadata": {},
  "billing": {
    "source": "wallet",
    "amount_micros": 1000,
    "package_grant_id": null,
    "refunded": false
  },
  "created_at": "2026-10-05T09:30:00.000Z",
  "sent_at": null,
  "delivered_at": null,
  "read_at": null,
  "failed_at": null
}
  • Channel types7 behind one endpoint
  • Message types9, from text to interactive lists
  • Price from$0.0003 per outbound message
  • Rate limit100 requests per second per key
  • Batch sizeUp to 100 messages per request
  • Webhook retries8, with backoff from 30 seconds to 24 hours
  • Idempotency window24 hours
  • Test modeFree, no channel required
  • Failed messagesRefunded automatically
  • Inbound messagesFree

How it works

From signup to a delivered message in four steps

There is no sales call and no minimum commitment. You can make your first API call in test mode a minute after creating an account.

  1. Step 01

    Create an account

    Sign up, verify your email and create an API key in the console. Test keys work immediately, before any channel is connected.

  2. Step 02

    Connect a channel

    Sign in with Facebook or TikTok in the console to connect a WhatsApp number, a Page or a business account. Add a Telegram bot or a Twilio number with its credentials, in the console or with POST /v1/channels.

  3. Step 03

    Send through one endpoint

    POST /v1/messages takes a channel ID, a recipient and a typed content object. The request shape is the same on every channel.

  4. Step 04

    Track every delivery

    Signed webhooks report sent, delivered, read and failed. The same history is in the console and at GET /v1/messages.

Message types

What you send is what they see

Each message has a type and a content object under the key of that type. Pick one to see the request body next to the message it produces.

POST /v1/messages
{
  "channel": "ch_7Hq2mN5vB8cX1zL0pK3j",
  "to": "+971501234567",
  "type": "text",
  "text": {
    "body": "Your order #1042 has shipped. Track it at https://example.com/t/1042",
    "preview_url": true
  },
  "reference": "order-1042"
}

Plain text, accepted by every channel type. Set preview_url to let the channel render a link preview.

ChannelsWhatsApp Business, SMS, SMS OTP, Telegram, Messenger, Instagram and TikTok

Console

A console for the parts that are not code

Create keys, connect channels, search the message log, replay webhook deliveries and manage billing. Everything the console shows is also available through the API.

Overview. Wallet balance, remaining package credits and the last 30 days of outbound volume, per account and per mode. The frames on this page are drawn with sample data.
Message log. Filter by channel, status, recipient or your own reference, and open any message to see its status history and what it was charged.
Billing. Top up the wallet, buy packages, set auto-recharge and download receipts. Package credits show what is left and when it expires.

Pricing

Pay per message, or buy messages in bulk

Top up a prepaid wallet from $10 and pay the per-message price of each channel, or buy a package of message credits for volume on your highest-priced channels.

10k messages

$8

$0.0008 per message

  • 10,000 outbound messages
  • Valid for 3 months from purchase
  • Valid on every channel type
Start with 10k

100k messages

Featured

$60

$0.0006 per message

  • 100,000 outbound messages
  • Valid for 6 months from purchase
  • Valid on every channel type
Start with 100k

1m messages

$400

$0.0004 per message

  • 1,000,000 outbound messages
  • Valid for 12 months from purchase
  • Valid on every channel type
Start with 1m

Pay as you go

Pay-as-you-go price per outbound message by channel type, in US dollars
ChannelPer message
WhatsApp Business$0.001
Telegram$0.0003
SMS$0.0005
SMS OTP$0.0005
Messenger$0.0005
Instagram$0.0005
TikTok$0.0005

What the price covers

  • BilledOutbound messages accepted by the API, at the moment they are accepted.
  • FreeInbound messages, test-mode messages, webhooks and the console.
  • RefundedAny message that ends as failed, back to the package or wallet it came from.
  • SeparateFees that Meta, carriers or other providers charge for the channel itself.

Developer experience

Built to be integrated once and left alone

Signed webhooks, safe retries, a sandbox that behaves like production and errors you can branch on.

Webhooks you can verify

Every delivery is signed with HMAC-SHA256 over the timestamp and the raw body, in the OmniMessage-Signature header. Answer with any 2xx within 10 seconds. Failed deliveries are retried eight times with backoff, from 30 seconds up to 24 hours.

verify-signature.js
import { createHmac, timingSafeEqual } from 'node:crypto';

// header is "t=<unix seconds>,v1=<hex hmac-sha256>"
export function verifySignature(rawBody, header, secret) {
  const parts = header.split(',').map((part) => part.split('='));
  const { t, v1 = '' } = Object.fromEntries(parts);

  const expected = createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  const matches =
    v1.length === expected.length &&
    timingSafeEqual(Buffer.from(v1), Buffer.from(expected));

  return fresh && matches;
}
message.delivered event
{
  "id": "evt_8Kd2pQ7wN4xB1zR6mT3c",
  "object": "event",
  "type": "message.delivered",
  "mode": "live",
  "created_at": "2026-10-05T09:30:02.900Z",
  "data": {
    "object": {
      "id": "msg_2b1Xw9aQ3rT8yU0pL4kZ",
      "object": "message",
      "status": "delivered",
      "reference": "order-1042",
      "delivered_at": "2026-10-05T09:30:02.871Z"
    }
  }
}

A test mode that costs nothing

Test keys use built-in sandbox channels, so there is nothing to connect. The last digits of the recipient decide the simulated outcome, and your webhooks fire as they would in production.

Sandbox request, ends as read
curl https://api.omnimessage.co/v1/messages \
  -H "Authorization: Bearer om_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "ch_test_whatsapp",
    "to": "+971501230002",
    "type": "text",
    "text": { "body": "Hello from the sandbox" }
  }'

Errors with a type and a code

Every non-2xx response has the same body: a type for the class of failure, a stable code to branch on, the offending param where there is one, and a request_id for support.

402 Payment Required
{
  "error": {
    "type": "billing_error",
    "code": "insufficient_balance",
    "message": "Not enough wallet balance or package credits.",
    "request_id": "req_5Vn1cH8jL3qW6yD9sF2k",
    "doc_url": "https://omnimessage.co/docs/errors#insufficient_balance"
  }
}
  • Idempotent POSTs

    Send an Idempotency-Key header and a retry within 24 hours returns the stored response with Idempotent-Replayed: true, without sending or charging twice.

  • Predictable limits

    100 requests per second per key on POST /v1/messages, 20 elsewhere. Every response carries RateLimit-Remaining, and a 429 carries Retry-After.

  • Batch sending

    POST /v1/messages/batch accepts up to 100 messages. Each item is accepted, rejected and billed on its own, and the 207 response reports them by index.

  • Scoped keys

    Give each key only the scopes it needs, such as messages:write or billing:read, and restrict it to an IP allowlist.

Questions

Before you integrate

The short answers. The documentation has the long ones.

Do I need my own WhatsApp number, bot or SMS number?

Yes. OmniMessage is a bring-your-own-channel gateway: you connect your own WhatsApp Cloud API number, Telegram bot, Twilio number or social account, and keep ownership of it. The channels page lists what each type needs.

What exactly am I billed for?

One charge per outbound message that the API accepts: a package credit if you have one, otherwise the per-message price of the channel type from your wallet. Inbound messages and test-mode messages are free, and a message that ends as failed is refunded automatically.

Are Meta, carrier or provider fees included?

No. The gateway fee covers the API, delivery tracking, webhooks and the console. Fees that Meta, Twilio or another provider charge for the channel itself stay between you and that provider.

How do I test without sending real messages?

Use a key that starts with om_test_. Every account has a sandbox channel per type, such as ch_test_whatsapp. Nothing is delivered or billed and statuses are simulated: a recipient ending in 0000 fails, 0001 stays sent, 0002 is also read, and anything else is delivered within about two seconds.

What happens when my balance runs out?

The API answers 402 insufficient_balance and nothing is queued, so you never owe money after the fact. You can subscribe to the balance.low event or turn on auto-recharge to top up the wallet when it drops below a threshold you choose.

Do I need an SDK?

No. The API is JSON over HTTPS with bearer authentication, so any HTTP client works. The documentation has examples in cURL, Node, Python and PHP.

Send your first message in test mode today

Create an account, copy a test key and call the API before you connect a single channel. Every new account starts with 100 free messages.