Get started
Quickstart
Send a first message in test mode, connect a channel, send a live message and receive its delivery receipt on a webhook.
You need a terminal with curl and about ten minutes. The first three steps run entirely in test mode: no channel, no balance and no payment method are required.
1. Create an account and a test key#
- Sign up and verify your email address.
- In the console, open API keys and create a key in test mode with full access.
- Copy the key. It starts with
om_test_and is shown only once.
Export it so that the samples below can read it. The cURL samples in these docs show a placeholder key inline; the other languages read OMNIMESSAGE_API_KEY from the environment.
export OMNIMESSAGE_API_KEY="om_test_xxxxxxxxxxxxxxxxxxxxxxxx"Check that the key works. GET /v1/me accepts any valid key and returns the account, the mode and the scopes of the key.
curl https://api.omnimessage.co/v1/me \
-H "Authorization: Bearer om_test_xxxxxxxxxxxxxxxxxxxxxxxx"{
"object": "account",
"id": "acc_8nM3bV6cX9zL2kJ5hG1f",
"name": "Acme Logistics",
"mode": "test",
"api_key": {
"id": "key_1qW4eR7tY0uI3oP6aS9d",
"name": "Quickstart",
"scopes": [
"messages:write",
"messages:read",
"channels:read",
"channels:write",
"webhooks:read",
"webhooks:write",
"billing:read"
]
},
"capabilities": [
"automation_events",
"webhook_filters",
"test_inbound",
"events_feed"
]
}2. Send a message in test mode#
Every account has a built-in sandbox channel per channel type, named ch_test_<type>. Send a text message to the WhatsApp sandbox channel. Any recipient works; the last four digits of to decide the simulated outcome, and a number ending in 0002 is delivered and then read.
curl https://api.omnimessage.co/v1/messages \
-H "Authorization: Bearer om_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"channel": "ch_test_whatsapp",
"to": "+15550100002",
"type": "text",
"text": {
"body": "Hello from test mode"
}
}'The API answers 202 Accepted with the message in status queued. billing.source is none: test-mode messages are never billed.
{
"id": "msg_7yU2iO5pA8sD1fG4hJ6k",
"object": "message",
"mode": "test",
"channel_id": "ch_test_whatsapp",
"channel_type": "whatsapp",
"direction": "outbound",
"to": "+15550100002",
"from": "sandbox",
"type": "text",
"content": {
"text": {
"body": "Hello from test mode"
}
},
"status": "queued",
"error": null,
"reference": null,
"metadata": {},
"billing": {
"source": "none",
"amount_micros": 0,
"package_grant_id": null,
"refunded": false
},
"sender": null,
"contact_id": null,
"created_at": "2026-10-05T09:30:00.000Z",
"updated_at": "2026-10-05T09:30:02.871Z",
"sent_at": null,
"delivered_at": null,
"read_at": null,
"failed_at": null
}3. Follow the delivery status#
Delivery is asynchronous. Within about two seconds the sandbox moves the message to sent, then delivered, then read. Retrieve the message with the id from the previous response:
curl https://api.omnimessage.co/v1/messages/msg_7yU2iO5pA8sD1fG4hJ6k \
-H "Authorization: Bearer om_test_xxxxxxxxxxxxxxxxxxxxxxxx"{
"id": "msg_7yU2iO5pA8sD1fG4hJ6k",
"object": "message",
"mode": "test",
"channel_id": "ch_test_whatsapp",
"channel_type": "whatsapp",
"direction": "outbound",
"to": "+15550100002",
"from": "sandbox",
"type": "text",
"content": {
"text": {
"body": "Hello from test mode"
}
},
"status": "read",
"error": null,
"reference": null,
"metadata": {},
"billing": {
"source": "none",
"amount_micros": 0,
"package_grant_id": null,
"refunded": false
},
"sender": null,
"contact_id": null,
"created_at": "2026-10-05T09:30:00.000Z",
"updated_at": "2026-10-05T09:30:02.871Z",
"sent_at": "2026-10-05T09:30:00.800Z",
"delivered_at": "2026-10-05T09:30:01.900Z",
"read_at": "2026-10-05T09:30:02.400Z",
"failed_at": null
}to ends in | Simulated outcome |
|---|---|
0000 | Fails with provider_error. |
0001 | Stays sent and is never delivered. |
0002 | sent, then delivered, then read. |
| Anything else | sent, then delivered within about two seconds. |
Polling is fine for a first test. In production, receive status changes on a webhook instead (step 6). Test mode describes everything the sandbox simulates.
4. Connect a channel#
To deliver real messages you connect one of your own senders. The simplest route is the console: open Channels, choose a type and follow the steps. WhatsApp, Telegram, SMS and SMS OTP channels can also be connected through the API with a live key that has the channels:write scope.
curl https://api.omnimessage.co/v1/channels \
-H "Authorization: Bearer om_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"type": "telegram",
"name": "Order bot",
"credentials": {
"access_token": "7312045981:AAH..."
}
}'The channel is created with connection_status: "pending" and becomes connected once the provider confirms the link, usually within seconds. Each channel guide lists the credentials to prepare.
{
"id": "ch_2pT5gB8nM1kL4jH7fD0s",
"object": "channel",
"mode": "live",
"type": "telegram",
"name": "Order bot",
"identifier": "acme_orders_bot",
"status": "active",
"connection_status": "pending",
"capabilities": [
"text",
"attachments",
"button",
"location",
"contacts",
"poll"
],
"created_at": "2026-10-01T08:00:00.000Z"
}5. Send a live message#
Create a live key in the console (it starts with om_live_) and make sure the account has funds: check GET /v1/balance for package credits or wallet balance, and top up in the console if both are zero. Then send the same request with the live key and your channel ID.
curl https://api.omnimessage.co/v1/messages \
-H "Authorization: Bearer om_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-shipped" \
-d '{
"channel": "ch_2pT5gB8nM1kL4jH7fD0s",
"to": "482910375",
"type": "text",
"text": {
"body": "Your order #1042 has shipped."
},
"reference": "order-1042"
}'Two details matter in production. The Idempotency-Key header makes the request safe to retry after a timeout: a repeat with the same key returns the first response instead of sending again. The reference field stores your own identifier on the message so that you can find it later.
6. Receive delivery receipts#
Register an HTTPS URL on your server. The response contains the signing secret; store it, because it is not shown again.
curl https://api.omnimessage.co/v1/webhook_endpoints \
-H "Authorization: Bearer om_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/omni",
"events": [
"message.delivered",
"message.failed",
"message.received"
]
}'{
"id": "we_3kL9pQ2wE5rT8yU1iO4a",
"object": "webhook_endpoint",
"mode": "live",
"url": "https://example.com/hooks/omni",
"description": "Production delivery receipts",
"events": [
"message.delivered",
"message.failed",
"message.received"
],
"status": "active",
"filters": {},
"metadata": {},
"source": null,
"created_by": {
"type": "api_key",
"id": "key_1qW4eR7tY0uI3oP6aS9d"
},
"has_verification_token": false,
"created_at": "2026-10-02T11:15:00.000Z",
"secret": "whsec_Zk8vQ2mX5cB7nL0pR3tY6wA9dF1gH4jK"
}OmniMessage now sends a POST to that URL for each subscribed event. Verify the OmniMessage-Signature header before trusting the body, answer with a 2xx status within 10 seconds, and do the real work asynchronously. The webhooks guide has verification code for Node.js, Python, PHP and Go.
{
"id": "evt_6hJ9kL2zX5cV8bN1mQ4w",
"object": "event",
"type": "message.delivered",
"mode": "live",
"created_at": "2026-10-05T09:30:02.900Z",
"data": {
"object": {
"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 code is 482910"
}
},
"status": "delivered",
"error": null,
"reference": "order-1042",
"metadata": {
"user_id": "u_17"
},
"billing": {
"source": "package",
"amount_micros": 0,
"package_grant_id": "grant_5tGh2Kp9LmQ4xW7nB1cD",
"refunded": false
},
"sender": null,
"contact_id": null,
"created_at": "2026-10-05T09:30:00.000Z",
"updated_at": "2026-10-05T09:30:02.871Z",
"sent_at": "2026-10-05T09:30:01.210Z",
"delivered_at": "2026-10-05T09:30:02.871Z",
"read_at": null,
"failed_at": null
}
}
}To try the endpoint without sending anything, call POST /v1/webhook_endpoints/{id}/test, which delivers a signed webhook.test event.