Datagroup Messaging API (1.0.0)

Download OpenAPI specification:

Send WhatsApp and SMS messages from your own systems and check delivery status — the API behind Datagroup's messaging automation. Authenticate with a per-tenant API key created in the Datagroup portal (Admin → API keys). The same key works for every channel.

Channels. Every request and response carries an explicit channel field:

whatsapp sms
What you send an approved template (templateName) free text (content)
Approval needed yes, by Meta no
Threading messages join a 24-hour conversation none — each SMS stands alone
Delivery states Sent → Delivered → Read Sent → Delivered (no read receipt)
Billed by length no yes — per 160-character segment

Omitting channel still defaults to whatsapp, so existing integrations are unaffected.

SMS is billed per segment. A message longer than 160 characters is split and billed as multiple segments (70 characters if it contains any non-GSM character, such as an emoji). The segments field on every SMS response tells you how many were billed.

Delivery-status webhooks — don't poll. Instead of calling GET /messages/{messageId} on a timer, register one HTTPS endpoint in the portal (Admin → API keys → Delivery webhook) and we POST every status change to it as it happens, for both channels. Polling still works, but at volume the webhook is the supported path.

Each POST carries:

{
  "event": "message.status",
  "messageId": "3f9c…",
  "providerMessageId": "wamid.HBg…",
  "channel": "whatsapp",
  "status": "Delivered",
  "to": "+27821234567",
  "templateName": "order_confirmation",
  "errorDetail": null,
  "occurredAt": "2026-08-09T13:04:31.6339263+00:00"
}

Verify the signature before trusting a payload. Two headers accompany the body:

Header Value
X-Datagroup-Timestamp Unix seconds when we signed
X-Datagroup-Signature sha256= + HMAC-SHA256 over the exact string {timestamp}.{raw body}, lowercase hex

Compute the HMAC with the signing secret shown once when you registered the endpoint, using the raw request body (not a re-serialised object — any whitespace difference changes the hash), and compare with a constant-time comparison. The timestamp is inside the signed string, so a captured payload cannot be replayed with a fresh one; reject anything older than a few minutes.

Delivery is at-least-once and unordered. Retries mean you may see the same status twice, and two transitions seconds apart can arrive out of order. Make your handler idempotent and trust occurredAt over arrival order. Return 2xx promptly — a non-2xx is retried five times, roughly a minute apart, after which the notification is dropped (your data is still available via GET /messages/{messageId}).

Not yet available: free-text WhatsApp replies within the 24-hour window, WhatsApp Flows, and inbound SMS replies.

Messages

Send templates and query delivery status.

Send a message (WhatsApp template or SMS)

Sends a message to a South African mobile number. What the body must contain depends on channel:

  • whatsapptemplateName is required; only approved templates can open a conversation. Supply bodyParameters in order if the template has {{1}}, {{2}}, ... placeholders (see GET /templates for each template's expected count). Sending an OTP? Authentication templates carry a copy-code button that Meta requires a parameter for; it is filled from your bodyParameters automatically, so send the code once and nothing else changes. Templates in other categories with a dynamic URL button need buttonParameters.
  • smscontent is required and carries the message text. SMS has no template-approval concept, so passing templateName on this channel is rejected rather than ignored. The response's segments tells you how many segments were billed.

Pass an Idempotency-Key header to make retries safe — resending the same key returns the original result instead of sending a second message. Reusing a key always replays the first result; it is not re-validated against a changed request body, so treat each key as single-use per logical send.

Authorizations:
ApiKeyAuth
header Parameters
Idempotency-Key
string

An opaque string unique to this logical send. Retrying with the same key replays the original result.

Request Body schema: application/json
required
channel
string
Default: "whatsapp"
Enum: "whatsapp" "sms"

Defaults to whatsapp when omitted.

to
required
string

South African mobile number, +27... or 0....

content
string

SMS only — the message text. Billed per 160-character segment (70 if it contains any non-GSM character, such as an emoji); the response's segments reports how many.

templateName
string

WhatsApp only — the approved template to send.

language
string

Meta language code. Defaults to the template's own language if omitted.

phoneNumberId
string <uuid>

Required only if you have more than one active WhatsApp number (see GET /phone-numbers).

bodyParameters
Array of strings

Positional values for the template's {{1}}, {{2}}, ... placeholders, in order. Omit or leave empty for a variable-free template.

object

WhatsApp only, and rarely needed. Values for dynamic URL buttons, keyed by the button's zero-based position as a string — e.g. {"0": "ORD-1029"}. Only templates whose button URL contains a placeholder need this.

Authentication templates (OTPs) do not need it. Their copy-code / one-tap button takes the same code as the body, so it is filled from bodyParameters automatically — just send the code once, as before.

headerImageUrl
string

Overrides the template's stored header image, if any.

Responses

Request samples

Content type
application/json
Example
{
  • "channel": "whatsapp",
  • "to": "+27821234567",
  • "templateName": "order_ready",
  • "language": "en_US",
  • "bodyParameters": [
    ]
}

Response samples

Content type
application/json
{
  • "messageId": "8540d774-4863-4d2b-b788-4ecb19412e85",
  • "providerMessageId": "string",
  • "status": "Sent",
  • "conversationId": "ee6e55e8-45fe-4a3e-9bc8-4669f9fdf77a",
  • "channel": "whatsapp",
  • "segments": 1
}

List messages (delivery reporting)

Keyset-paginated list of your outbound messages, newest first. Defaults to the last 30 days if from/to are omitted.

Authorizations:
ApiKeyAuth
query Parameters
channel
string
Enum: "whatsapp" "sms"

Omit to list every channel.

status
string
Enum: "Queued" "Sent" "Delivered" "Read" "Failed"
templateName
string
phoneNumberId
string <uuid>
from
string <date-time>

ISO 8601 datetime; defaults to 30 days ago.

to
string <date-time>

ISO 8601 datetime; defaults to now.

limit
integer [ 1 .. 200 ]
Default: 50
cursor
string

Opaque token from a prior page's nextCursor. Omit for the first page.

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "nextCursor": "string"
}

Get one message's delivery status

Authorizations:
ApiKeyAuth
path Parameters
messageId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "messageId": "8540d774-4863-4d2b-b788-4ecb19412e85",
  • "to": "string",
  • "direction": "Outbound",
  • "templateName": "string",
  • "status": "Queued",
  • "timestamp": "2019-08-24T14:15:22Z",
  • "errorDetail": "string",
  • "conversationId": "ee6e55e8-45fe-4a3e-9bc8-4669f9fdf77a",
  • "channel": "whatsapp",
  • "providerMessageId": "string",
  • "segments": 0
}

Catalog

What you can send, and from which number.

List approved templates you can send

Includes the derived bodyParameterCount so you know how many bodyParameters a send needs.

Authorizations:
ApiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

List your active WhatsApp numbers

Only relevant when you have more than one number — with exactly one, POST /messages uses it automatically and phoneNumberId can be omitted.

Authorizations:
ApiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}