Home / Docs / REST API
Developer Reference · REST API

MsgHub REST API

Programmatic access to MsgHub through one REST API. Auth with a Bearer API key, send template messages on any channel, manage contacts, track events, and trigger campaigns. JSON over HTTPS. One unified POST /messages/send endpoint — the template's channel determines whether the message is dispatched as SMS, WhatsApp, Email or RCS.

REST · JSON over HTTPS Bearer auth v1 Last updated: 2026-05-14

Quick start

Send your first message in 60 seconds. Two steps: create the contact, then send a message referencing one of your approved templates. Replace YOUR_API_KEY with a key generated from the API Keys page in the sidebar, and TEMPLATE_UUID with a template ID from Templates.

# 1. Upsert the contact
curl https://app.msghub.info/api/v1/contacts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+919876543210", "name": "Riya" }'

# 2. Send a message using an approved template
curl https://app.msghub.info/api/v1/messages/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to":         { "phone": "+919876543210" },
    "templateId": "TEMPLATE_UUID"
  }'

# Response (201 Created)
{
  "success": true,
  "data": {
    "messageId": "",
    "status": "queued"
  }
}

Authentication

Every request must include a Bearer API key in the Authorization header:

Authorization: Bearer mh_live_xxxxxxxxxxxxxxxxxxxxxxxx

API keys are scoped to a single tenant. To create one: sidebar → API Keys → "+ New API Key". Pick a name and tick the scopes you need. Keys are shown once at creation — copy and store securely.

Never ship API keys in front-end code. Anyone with the key can send messages from your account. Keep server-side only. If a key leaks, revoke from the API Keys page.

Scopes

Pick the minimum set when creating a key:

ScopeWhat it allows
contacts:writeCreate / update contacts
contacts:readRead contacts
messages:sendSend messages via approved templates
campaigns:triggerTrigger a draft campaign
events:writeTrack custom events for a contact
templates:readList approved templates

Base URL & versioning

https://app.msghub.info/api/v1

The dashboard and API share the same domain. All public endpoints live under the /api/v1 prefix. Future breaking changes ship as /api/v2.

Request format

Response format

Every response uses a consistent envelope: { success: true, data: … } on the happy path, { success: false, error: { code, message } } on failure.

// Successful POST → 201 Created
{
  "success": true,
  "data": {
    "id": "",
    "created": true
  }
}

// List GET → 200 OK
{
  "success": true,
  "data": [ /* rows */ ]
}

// Failure
{
  "success": false,
  "error": { "code": "VALIDATION_ERROR", "message": "…" }
}

Error codes

All errors return the failure envelope above. Common codes you'll encounter:

HTTPCodeMeaning
400VALIDATION_ERRORRequest body failed schema validation
401UNAUTHENTICATEDMissing or invalid API key (Authorization: Bearer … required)
403FORBIDDENAPI key lacks the required scope
404NOT_FOUND / CONTACT_NOT_FOUNDTemplate or contact missing (create contact first)
422TEMPLATE_NOT_APPROVEDTemplate exists but isn't approved yet
422CONTACT_BLOCKEDContact is inactive or blocked
422CONTACT_DNDContact opted out of this channel
422NO_EMAILEmail template requires a contact with an email address
429RATE_LIMITEDToo many requests — slow down
500INTERNAL_ERRORServer error — safe to retry after backoff

Rate limits

Exceeding the cap returns:

HTTP 429 Too Many Requests
{
  "success": false,
  "error": { "code": "RATE_LIMITED", "message": "Too many requests. Please slow down." }
}

Honour the response and back off. Higher limits available on request for enterprise plans.

Pagination

List endpoints (e.g. GET /contacts) use simple page + limit query parameters:

GET /api/v1/contacts?page=1&limit=50

# Response
{
  "success": true,
  "data": [ /* up to `limit` rows */ ]
}

page is 1-indexed. limit defaults to 50, capped at 100. Increment page until you receive an empty array.

Endpoints

Six public endpoints under /api/v1. There is no per-channel send endpoint — one unified /messages/send with a templateId handles every channel. Conversation listing and template submission are dashboard-only for now.

Send a message

POST /api/v1/messages/send

Body parameters

toobjectrequired{ phone?, email? } — at least one. Contact must exist in this tenant.
templateIdUUIDrequiredUUID of an approved template. The template's channel (sms / whatsapp / email / rcs) determines the dispatch route.

Required scope

messages:send

Example request

POST /api/v1/messages/send
Authorization: Bearer mh_live_...
Content-Type: application/json

{
  "to":         { "phone": "+919876543210" },
  "templateId": "e2f8b1a0-…-uuid"
}

Response 201

{
  "success": true,
  "data": {
    "messageId": "",
    "status": "queued"
  }
}

Failure modes

  • NOT_FOUND — template doesn't exist
  • TEMPLATE_NOT_APPROVED — template still pending/rejected
  • CONTACT_NOT_FOUND — create contact first via POST /api/v1/contacts
  • CONTACT_BLOCKED, CONTACT_DND, NO_EMAIL — contact-state issues

To send DLT-compliant SMS, the underlying template must be approved on your DLT portal and registered against your SMS provider — see DLT SMS. The API behaves the same regardless; only the template metadata differs.

Contacts

POST /api/v1/contacts

Body parameters

phonestringPhone with country code, e.g. +919876543210
emailstringEmail address
namestringDisplay name
tagsstring[]Tags to add (merged with any existing)
metaobjectCustom fields, merged into the contact's customFields

At least one of phone or email is required. Upsert behaviour: if a contact with the same phone (or email) exists, it's updated and the response returns { created: false }.

Submitting an identifier is treated as consent for the channels provided (smsConsent + waOptIn for phone, emailConsent for email).

Required scope: contacts:write

Response

// 201 Created (new) or 200 OK (updated)
{
  "success": true,
  "data": { "id": "", "created": true }
}
POST /api/v1/contacts/unsubscribe

Mark a contact as DND (do-not-disturb). Body: { phone? , email? } — at least one. Matching contacts are flagged isDnd: true and excluded from non-email sends.

Required scope: contacts:write

GET /api/v1/contacts

Query parameters

pageinteger1-indexed page number (default 1)
limitintegerPer-page count (default 50, max 100)

Required scope: contacts:read

Returns { success: true, data: [ { id, phone, email, name, isDnd, isActive, tags, source, createdAt }, … ] }.

Events

POST /api/v1/events

Body parameters

eventstringrequiredEvent name (max 100 chars), e.g. cart.abandoned
contactobjectrequired{ phone?, email? } identifying the contact

Custom events feed into automation triggers. Use them to fire flows on real business events: order completion, cart abandonment, NPS submission, etc.

Required scope: events:write

Trigger a campaign

POST /api/v1/campaigns/trigger

Launch a campaign that's currently in draft state. Body: { campaignId: UUID }. The campaign is queued for dispatch and its state moves to processing.

Required scope: campaigns:trigger

Returns 422 INVALID_STATE if the campaign isn't a draft.

Templates

GET /api/v1/templates

Query parameters

channelstringFilter by channel: sms · whatsapp · email · rcs

Returns only approved templates. Submit new templates for approval from the dashboard Templates page — the API doesn't currently expose template creation.

Required scope: templates:read

Response shape:

{
  "success": true,
  "data": [
    { "id": "", "name": "order_shipped", "channel": "whatsapp", "smsBody": null }
  ]
}

Webhooks

MsgHub can push real-time events to your server — delivery receipts, contact unsubscribes, campaign completion. Add endpoints from the Webhooks page in the sidebar (top-level item, not nested under Settings).

Webhooks are signed with HMAC-SHA256. The signature header uses a Stripe-style t={timestamp},v1={signature} format. Verify on every incoming request.

Full Webhooks Documentation →
Event catalog (6 types), HMAC verification (Node/Python/PHP), security checklist.

SDKs & examples

A JavaScript SDK is available at https://app.msghub.info/api/v1/sdk.js — drop one script tag and call MsgHub.identify(), track(), send(). For server-side, use any HTTP client.

Browser SDK (no install)

<!-- in your HTML -->
<script>
  window.MsgHub = {
    apiUrl: 'https://app.msghub.info',
    apiKey: 'mh_live_…'  // public-scope only — never an admin key
  };
</script>
<script src="https://app.msghub.info/api/v1/sdk.js" async></script>

<script>
  // Identify the visitor (creates / updates the contact)
  MsgHub.identify({ phone: '+919876543210', email: '[email protected]', name: 'Ravi' });

  // Track a custom event
  MsgHub.track('cart.abandoned', { phone: '+919876543210' });
</script>

Node.js (vanilla)

const base = 'https://app.msghub.info/api/v1';
const headers = {
  'Authorization': `Bearer ${process.env.MSGHUB_KEY}`,
  'Content-Type': 'application/json',
};

// 1. Upsert the contact
await fetch(`${base}/contacts`, {
  method: 'POST', headers,
  body: JSON.stringify({ phone: customer.phone, name: customer.name }),
});

// 2. Send an approved template
const res = await fetch(`${base}/messages/send`, {
  method: 'POST', headers,
  body: JSON.stringify({
    to:         { phone: customer.phone },
    templateId: 'e2f8b1a0-…-uuid',
  }),
});
const data = await res.json();
if (!data.success) throw new Error(data.error.message);

Python (requests)

import requests, os

base    = "https://app.msghub.info/api/v1"
headers = { "Authorization": f"Bearer {os.environ['MSGHUB_KEY']}" }

# Upsert contact
requests.post(f"{base}/contacts", headers=headers,
              json={"phone": customer.phone, "name": customer.name})

# Send approved template
resp = requests.post(f"{base}/messages/send", headers=headers,
                     json={ "to": {"phone": customer.phone}, "templateId": template_id })
resp.raise_for_status()
data = resp.json()
if not data["success"]:
    raise Exception(data["error"]["message"])

PHP (cURL)

$base    = 'https://app.msghub.info/api/v1';
$headers = [
    'Authorization: Bearer ' . getenv('MSGHUB_KEY'),
    'Content-Type: application/json',
];

// Send approved template
$ch = curl_init($base . '/messages/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => $headers,
    CURLOPT_POSTFIELDS     => json_encode([
        'to'         => ['phone' => $customer->phone],
        'templateId' => $templateId,
    ]),
]);
$data = json_decode(curl_exec($ch), true);

Other integrations

For point-and-click integration without writing code:

FAQ

How do I get an API key?

In MsgHub, click API Keys in the left sidebar (it's a top-level item — not nested under Settings). Click + New API Key, choose a name and the scopes you need, then click Create. The key is shown once — copy it immediately. Keys use the format mh_live_<hex>.

What's the API base URL?

https://app.msghub.info/api/v1. The dashboard and API share the same hostname; the API lives under /api/v1.

Is there a JavaScript SDK?

Yes — drop https://app.msghub.info/api/v1/sdk.js in your page, set window.MsgHub = { apiUrl, apiKey }, and call MsgHub.identify(), track(), or send(). Node, Python and PHP wrappers aren't yet packaged; use any HTTP client meanwhile.

What are the rate limits?

300 requests/minute per source IP globally. Researcher-tier tenants are tighter at 60 req/min per API key. Hitting the limit returns 429 with error.code = "RATE_LIMITED" — slow down and retry.

Is there an Idempotency-Key header?

Not currently. Each POST /messages/send creates a separate message record. If your application retries on failure, dedupe on your side (e.g. by the response messageId the first call returned). Idempotency-key support is on the roadmap.

Can I send via multiple channels in one request?

One template = one channel. Choose the template whose channel matches what you want to send. If you want a fallback (try WhatsApp, fall back to SMS), implement it client-side: call /messages/send for WhatsApp first, listen to message.failed webhook, then call again with the SMS template.

Why does my send return CONTACT_NOT_FOUND?

The contact must exist in your tenant before you can send to it. Create or upsert the contact via POST /api/v1/contacts first (with at least a phone or email). Then POST /messages/send with the same identifier.

Why does my send return TEMPLATE_NOT_APPROVED?

WhatsApp templates need Meta approval (1–2 hours typically). DLT SMS templates need DLT-portal approval. Only templates in approved state are dispatchable. List approved templates with GET /api/v1/templates.

Are API keys per-tenant or per-user?

Per-tenant. Keys aren't tied to a user account, so they keep working when team members leave. Every API call is logged with the key that made it for audit purposes.

What's next

Set up webhooks
Receive real-time events for inbound messages, delivery receipts, and conversation state changes.
SMPP for high-volume SMS
When REST isn't fast enough for OTP-scale traffic, bind via SMPP 3.4 for persistent-connection throughput.
Need integration help?
Our team will review your architecture and provide a sandbox tenant for testing.