Skip to content
MessageHub

For developers

API documentation

Everything you need to connect your system to MessageHub: authentication, OTP codes, template messages, balance and notifications.

Base URL

All requests are JSON over HTTPS to this base URL:

https://api.message-hub.cloud/api/v1

Authentication

Every request carries your key in the Authorization header. You find it in the dashboard; it is shown only once when created, so store it safely and never ship it in browser or app code.

Authorization: Bearer mh_live_xxxxxxxxxxxxxxxxxxxxxxxx

Phone numbers are accepted in local (07701234567) or international (9647701234567) format.

Endpoints

MethodEndpointDescription
POST/otp/sendSend an OTP to a WhatsApp number
POST/otp/verifyVerify a code the user entered
GET/otp/{id}Status of an OTP
POST/messagesSend a template or text message
GET/messagesList your messages (status, to, per_page)
GET/messages/{id}Status of one message
GET/accountBalance, prices and sender number

Send an OTP

We generate the code and send it with the approved authentication template. Keep the id for verification.

FieldDescription
phonerequiredThe customer's number.
lengthoptionalCode length, 4 to 10 (default 6).
expires_inoptionalValidity in minutes, 1 to 60 (default 5).
codeoptionalYour own code (4–10 digits) if you prefer to generate it yourself.
referenceoptionalYour request identifier (up to 100 characters) — makes retries safe.
Request
curl -X POST https://api.message-hub.cloud/api/v1/otp/send \
  -H "Authorization: Bearer $MESSAGEHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone": "07701234567", "length": 6, "expires_in": 5, "reference": "login-123"}'
Response
201 Created
{
  "data": {
    "id": "9d3c8f2e-…",
    "phone": "9647701234567",
    "status": "pending",
    "expires_at": "2026-11-05T21:35:00+03:00",
    "message": { "id": "…", "status": "sent", "cost": …, "currency": "IQD" }
  },
  "balance": …
}

Verify the code

Send the id (or the phone) with the code the user entered. You get 200 when it is valid and 422 with a reason when it is not.

Request
curl -X POST https://api.message-hub.cloud/api/v1/otp/verify \
  -H "Authorization: Bearer $MESSAGEHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "9d3c8f2e-…", "code": "482913"}'
Response
200 OK
{ "data": { "valid": true, "reason": null, "otp": { … } } }

422 Unprocessable Entity
{ "data": { "valid": false, "reason": "invalid_code", "otp": { … } } }

Reasons: invalid_code (wrong code), expired, too_many_attempts, already_verified.

GET /otp/{id}
curl https://api.message-hub.cloud/api/v1/otp/9d3c8f2e-… \
  -H "Authorization: Bearer $MESSAGEHUB_KEY"

Template messages

Send a notification with an approved WhatsApp template and its variables. Category utility for notifications, marketing for promotions.

Request
curl -X POST https://api.message-hub.cloud/api/v1/messages \
  -H "Authorization: Bearer $MESSAGEHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "07701234567",
    "template": "order_update",
    "language": "ar",
    "variables": ["1045", "غداً"],
    "category": "utility",
    "reference": "order-1045-shipped"
  }'
curl https://api.message-hub.cloud/api/v1/messages/{id} -H "Authorization: Bearer $MESSAGEHUB_KEY"
curl "https://api.message-hub.cloud/api/v1/messages?status=delivered&per_page=50" -H "Authorization: Bearer $MESSAGEHUB_KEY"

Message statuses: queued, sent, delivered, read, failed.

Balance and prices

Returns your account name, your balance in IQD, the price of each category and the sender number.

curl https://api.message-hub.cloud/api/v1/account -H "Authorization: Bearer $MESSAGEHUB_KEY"

{
  "data": {
    "name": "…",
    "balance": …,
    "currency": "IQD",
    "prices": { … },
    "sender": { "phone_number": "…", "display_name": "…", "shared": true }
  }
}

Safe retries (reference)

Sending the same reference twice returns the same message — no new send and no new charge. Always use it when retrying after a network error.

Balance and refunds

  • Insufficient balance → 402 insufficient_balance and nothing is sent.
  • Meta rejects the message → 502 send_failed and the amount returns to your balance immediately. If Meta reports a failure later, it is refunded too.
402 Payment Required
{ "message": "Insufficient balance.", "code": "insufficient_balance" }

Status notifications (webhook)

When a message status changes we send a POST request to the webhook URL you set in the dashboard.

POST https://your-app.example/webhooks/messagehub
Content-Type: application/json
X-MessageHub-Signature: sha256=<hex hmac_sha256(raw_body, webhook_secret)>

{
  "event": "message.status",
  "data": {
    "id": "…",
    "reference": "login-123",
    "to": "9647701234567",
    "status": "delivered",
    "category": "authentication",
    "cost": …,
    "refunded": false,
    "error": null,
    "updated_at": "2026-11-05T21:31:04+03:00"
  }
}

Each request carries an X-MessageHub-Signature header: sha256= followed by the HMAC-SHA256 of the raw body with your webhook secret. Compute it and compare in constant time before trusting the request.

Verifying the signature
import { createHmac, timingSafeEqual } from 'node:crypto'

function isValid(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(header ?? '')
  return a.length === b.length && timingSafeEqual(a, b)
}

Error codes

Every error returns JSON with a message and a code.

401invalid_api_keyThe key is missing or invalid.
403account_suspendedThe account is suspended.
403ability_missingThe key lacks the ability for this endpoint (otp or messages).
403ip_not_allowedThis IP address is not allowed for the key.
402insufficient_balanceNot enough balance; nothing was sent.
422validationThe request data is invalid.
429rate_limitedToo many requests; retry shortly.
502send_failedMeta rejected the message and the amount was refunded.
503no_sender · no_otp_templateNo active sender number or OTP template yet.