Messaging Overview
Messaging runs as its own service at messaging.sip.io, a pure HTTP + data plane (no SIP/media), separate from the voice edge but sharing the same account, numbers, and data model. It does three things: receive inbound messages from your providers, send outbound ones, and route inbound conversations into the same agent/queue system that handles voice calls, so agents work one inbox for both. See Omnichannel Inbox & ACD for the routing, agent-inbox, and webhook side; this page covers channels and sending.
Channels
Section titled “Channels”| Channel | Status | Notes |
|---|---|---|
| SMS | Live | Carrier-neutral (BYOC) via a config-driven HTTP adapter. |
| MMS | Live | Media is carried on an SMS/WhatsApp message (the media array), not a separate channel. |
| Live | Cloud API; templates + 24-hour session window. | |
| RCS | Roadmap | Modeled (rcs channel, RBM agent address), but no provider adapter yet. |
The data model
Section titled “The data model”Six tables, all account-scoped:
| Table | What it holds |
|---|---|
messaging_channel | A sender: channel (sms/whatsapp/rcs), provider (adapter key), address, provider refs, default_queue_id, inbound-forwarding config, and an inbound signature-verify secret. UNIQUE(channel, address). |
conversation | One thread per (channel_id, contact_address): status (open/assigned/closed/snoozed), assigned_agent_id, queue_id, unread, and the WhatsApp window_expires_at. |
message | direction (inbound/outbound), body, media, status, provider_message_id, and the template_id used. |
message_template | WhatsApp/RCS templates: name, language, category, body with {{1}} placeholders, approval status. |
message_opt_out | The per-account STOP list: (channel, contact_address). |
chat_assignment / chat_queue | Live omnichannel ACD state (in the PresenceEngine, not the edge SQL database). See Omnichannel Inbox & ACD. |
A contact’s whole history lives in one conversation; messages link to it by conversation_id.
Managing channels (/v1/channels)
Section titled “Managing channels (/v1/channels)”A channel is a number or sender identity (an SMS long code, a WhatsApp Business number, an RCS agent). Channels are managed through the public API, scope channels:
curl -X POST https://api.sip.io/v1/channels \ -H 'x-api-key: sk_…' -H 'content-type: application/json' \ -d '{ "channel": "sms", "provider": "generic_http", "address": "+14155550100", "providerSecret": "carrier-api-key", "defaultQueueId": "q_support", "forwardEmail": "support@acme.com", "inboundSecret": true }'# → { ok, id, channel, provider, address, inboundUrl, inboundVerification: true, inboundSecret: "whsec_…" (shown once) }| Field | Purpose |
|---|---|
channel | sms | whatsapp | rcs. |
provider | The adapter key: generic_http (config-driven, any carrier), meta (WhatsApp Cloud API), smpp (stub, not wired). |
address | The customer-facing sender: an E.164 number (SMS/WhatsApp) or an RBM agent id. |
providerAccountRef | The provider’s account id (e.g. a Meta WABA phone-number id). |
providerSecret | The provider’s API credential. Stored in edge runtimes the key-value store via a msg_prov:<id> reference, never returned by GET. |
defaultQueueId | Inbound messages on this channel route into this queue (the omnichannel ACD, see Omnichannel Inbox & ACD). Must belong to your account. |
forwardEmail | CSV of email addresses; inbound messages are also mirrored to email. See Inbound forwarding. |
forwardWebhookUrl / forwardWebhookSecret | Per-channel inbound forward to a URL. See Inbound forwarding. |
inboundSecret | true to generate a signature-verification secret (shown once), a string to set your own, or omit to leave inbound unverified. See Verifying inbound webhooks. |
metadata | A JSON object; for generic_http this holds the carrier’s send/inbound field mapping (see below). |
GET /v1/channels lists channels for the account (secrets are never returned, only presence flags: has_provider_secret, inbound_verification, has_forward_webhook_secret); GET /v1/channels/{id} returns one. PATCH /v1/channels/{id} updates an allowlisted subset of fields (status, address, defaultQueueId, forwardEmail, forwardWebhookUrl, metadata, plus secret rotation: providerSecret, forwardWebhookSecret, and inboundSecret where true rotates/arms, a string sets, null clears/disarms). DELETE /v1/channels/{id}?confirm=1 deletes the channel; the confirm=1 guard exists because conversations and messages cascade with it. PATCH {status:'disabled'} is the non-destructive alternative when you want to keep history.
Every channel gets a public inbound URL, inboundUrl in the response, of the form https://messaging.sip.io/inbound/{channelId}, which you register with your provider as its delivery webhook.
Sending
Section titled “Sending”curl -X POST https://messaging.sip.io/send \ -H 'x-api-key: sk_…' -H 'content-type: application/json' \ -d '{ "channelId": "ch_…", "to": "+14155550100", "body": "Hi from SIP.IO" }'POST /send requires the messaging scope. Body: { channelId, to, body | templateName | mediaUrls, templateLang, templateVars }. It:
- blocks opted-out recipients (
403), - enforces the WhatsApp 24h window: outside it, a
templateNameis required, else422, - dispatches through the channel’s adapter and stores an outbound
message, - finds or creates the conversation for
(channelId, to), so a proactive outbound message and a later inbound reply land in the same thread.
Returns { ok, messageId, conversationId, providerMessageId, status }.
The same channel-centric send is also available as POST /v1/messages on the public API (scope messaging), body { channelId, to, body | templateName | mediaUrls, templateLang, templateVars } - functionally the same operation, reused by both the messaging worker and the API facade so behavior is identical.
Receiving
Section titled “Receiving”Each channel has a public inbound URL, POST /inbound/{channelId}, that you register with your provider. On an inbound message the service:
- verifies the request signature, if the channel has one configured (see Verifying inbound webhooks),
- normalizes the provider payload through the channel’s adapter,
- handles STOP/START opt-out keywords,
- upserts the conversation (reopening a closed one, resetting the WhatsApp window),
- stores the inbound
message, - routes the conversation to a chat-capable agent if the channel has a
default_queue_id(see Omnichannel Inbox & ACD), - fires the
message.receivedwebhook and any configured inbound forwarding.
Provider status callbacks (delivered/read/failed) arrive on the same URL and update the message by provider_message_id, firing message.status.
Message status is one of queued, sent, delivered, read, failed, received.
Verifying inbound webhooks
Section titled “Verifying inbound webhooks”POST /inbound/{channelId} is a public URL: without verification, anyone who learns a channel id could forge inbound messages (inject into conversations, trigger ACD routing or email/webhook forwards, plant a STOP opt-out). Verification is opt-in but fail-closed: set inboundSecret on the channel and every inbound request must pass signature verification, including the case where the stored secret is somehow missing (that fails closed too, not open). Leave inboundSecret unset and the channel accepts unverified inbound, the pre-verification default, kept for back-compat and local testing.
The scheme depends on the channel’s provider:
| Provider | Scheme |
|---|---|
meta (WhatsApp Cloud API) | Fixed: X-Hub-Signature-256 header, sha256= + hex HMAC-SHA256 of the raw body, keyed by the app secret. The GET subscription handshake (hub.mode=subscribe&hub.verify_token=…&hub.challenge=…) is also verified against the same secret before the challenge is echoed back. |
generic_http | Config-driven via metadata.inbound.verify, since carriers vary: hmac (a signature header, configurable header name/algo sha1|sha256/encoding hex|base64/prefix like sha256=), token (a static shared token in a header or query param), basic (HTTP Basic auth, the secret is the literal user:pass), or twilio (the X-Twilio-Signature scheme: base64 HMAC-SHA1 over the URL plus sorted form params, for BYOC-Twilio channels). |
All comparisons are constant-time. Signatures are always computed over the raw request body, verified before the payload is parsed. A failed verification returns 401 and the message is discarded, not stored, not routed, not forwarded.
Inbound forwarding
Section titled “Inbound forwarding”Two independent, per-channel forwarding options mirror an inbound message elsewhere, alongside the normal conversation/webhook path. Both are best-effort and never block the provider’s 200 acknowledgment.
SMS-to-email
Section titled “SMS-to-email”Set forwardEmail on a channel to a CSV of email addresses (up to 50). Every inbound message on that channel is mirrored as an email via the edge platform Email Sending, sent from messaging@sip.io, with the sender, recipient, timestamp (UTC), body, and any media links, in both plain text and HTML.
SMS-to-webhook
Section titled “SMS-to-webhook”Set forwardWebhookUrl (must be https://) and optionally forwardWebhookSecret on a channel. Every inbound message on that channel is POSTed to that URL in the same envelope shape as the account-wide webhook delivery, { event: "message.received", account_id, data, ts }, signed the same way (x-sipio-signature: sha256=<hex hmac> if a secret is set), so a consumer can verify a per-channel forward identically to an account-level webhook subscription. This is the per-number sibling of the account-wide webhook_subscription fan-out: use it when a single number’s traffic should go to its own endpoint rather than (or in addition to) your account’s general webhook subscriptions.
Carrier-neutral adapters
Section titled “Carrier-neutral adapters”Providers are pluggable behind a small adapter interface, selected by the channel’s provider field:
generic_http: a regional SMS carrier or aggregator defined entirely in the channel’smetadata(the request URL, auth, and field mapping), so no code is needed to add a carrier.- WhatsApp Cloud API (
meta), for thewhatsappchannel. - SMPP and RCS/RBM adapters are planned;
smppexists as a registered provider key but the adapter isn’t wired yet.
Roadmap
Section titled “Roadmap”WhatsApp template management, inbound signature verification for the rcs adapter once RCS ships, and the SMPP adapter.