API v1

Developer Documentation

Send WhatsApp messages from your own systems, and receive every reply into them.

Contents
  • Getting Started

  • Authentication

  • Sending Messages

  • Receiving Messages

  • Templates

  • Errors & Limits

Getting Started


A REST API over HTTPS. It is available to every account — nothing has to be enabled by an administrator. A key acts as its owner, on the WhatsApp number that owner connected, and can never see or send from anyone else's.

Base URL
// Base URLhttps://meta.sanjusk.in/api/v1
Three steps to your first message
  1. Sign in and connect a WhatsApp number under Platform → Numbers.

  2. Go to Developers → API keys and create a key. It is shown once — store it where your code reads its secrets.

  3. Call GET /api/v1/status to confirm the key works and see which number it sends from.

  4. Send with POST /api/v1/send-template, or POST /api/v1/send-text if the person messaged you in the last 24 hours.

Check your key
// Shellcurl https://meta.sanjusk.in/api/v1/status \
  -H "Authorization: Bearer mbsp_your_key_here"

# {
#   "success": true,
#   "data": {
#     "connected": true,
#     "phoneNumberId": "123456789012345",
#     "displayPhoneNumber": "+91 98765 43210",
#     "verifiedName": "Acme Support",
#     "connectionMode": "embedded_signup",
#     "qualityRating": "GREEN",
#     "status": "active"
#   }
# }

Authentication


Every request carries an API key as a bearer token. There is no separate token exchange step and no session to refresh — the key you create is the credential.

// ShellAuthorization: Bearer mbsp_your_key_here

Keys are stored hashed, so a lost key cannot be recovered — revoke it and create another. Revocation takes effect immediately. Never put a key in a repository, a mobile app or a front-end bundle: anyone holding it can send messages as you.

What a key can do

A key reaches every endpoint on this page, for the one WhatsApp number its owner connected — there are no per-endpoint scopes to choose between. Create a separate key per integration so you can revoke one without disturbing the others, and name it after the system that holds it.

Send messages
Read messages
Read templates

Sending Messages


Which endpoint you need is decided by Meta's rule, not by preference: you may send free-form content only within 24 hours of that person's last message to you. Outside that window it must be an approved template. If you are starting the conversation, it is always a template.

Template message

Works at any time. Reach for this by default.

// Shellcurl -X POST https://meta.sanjusk.in/api/v1/send-template \
  -H "Authorization: Bearer mbsp_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "919876543210",
    "template": "order_shipped",
    "language": "en_US",
    "components": [
      {
        "type": "body",
        "parameters": [{ "type": "text", "text": "AC-1042" }]
      }
    ]
  }'
Free-form text

Inside the 24-hour window only. Outside it you get a 403 with code: "OUTSIDE_24H_WINDOW" telling you to send a template instead — handle that case rather than treating it as an outage.

// Shellcurl -X POST https://meta.sanjusk.in/api/v1/send-text \
  -H "Authorization: Bearer mbsp_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "919876543210", "text": "Your order is on its way." }'
Media

An image, video, audio file, document or sticker, by public HTTPS URL. Same 24-hour rule as text.

// Shellcurl -X POST https://meta.sanjusk.in/api/v1/send-media \
  -H "Authorization: Bearer mbsp_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "919876543210",
    "type": "image",
    "link": "https://example.com/invoice.png",
    "caption": "Your invoice",
    "filename": "invoice.png"
  }'

Receiving Messages


Two ways in, delivering the same messages. Pick the one that matches where your code runs — you do not need both.

Option 1 — we push to you

Add your endpoint under Developers → Webhook destinations in the dashboard. Every inbound message on your number is POSTed to it as it arrives. You can register several, one per project, each with its own signing secret. A failed delivery is retried twice, after 5 and 15 seconds.

// HTTPPOST https://your-app.example/webhooks/whatsapp
Content-Type: application/json
X-Metabsp-Event: message.received
X-Metabsp-Signature-256: sha256=<hmac of the raw body, using your secret>

{
  "from": "919876543210",
  "to": "15550001111",
  "phoneNumberId": "123456789012345",
  "type": "text",
  "message": "Where is my order?",
  "messageId": "wamid.HBgM...",
  "timestamp": "2026-09-01T10:00:00.000Z"
}

Your URL is reachable by anyone who guesses it, so the header is what proves the request came from us. Compute the HMAC over the raw body — parse the JSON only after it matches, and compare in constant time.

// Node.jsconst crypto = require('crypto');

app.post('/webhooks/whatsapp', express.raw({ type: 'application/json' }), (req, res) => {
  const expected =
    'sha256=' + crypto.createHmac('sha256', process.env.METABSP_WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');
  const received = req.get('X-Metabsp-Signature-256') || '';

  const ok =
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!ok) return res.sendStatus(403);

  const event = JSON.parse(req.body);
  // Answer within a few seconds; do the real work afterwards.
  res.sendStatus(200);
});
Routing between several destinations

If you register more than one destination, give each an entry keyword. A message starting with that keyword is routed to that destination, and the rest of that conversation stays with it until the person goes quiet or sends STOP. A message that matches no keyword goes to every destination that has no keyword of its own, plus any marked as a fan-out fallback. With a single destination and no keyword, you simply receive everything.

Option 2 — you poll us

For anything that cannot host a public endpoint: a desktop tool, a script behind NAT, a scheduled job. Pass the nextSince value from the previous response and you cannot miss a message or receive one twice. The cursor is a timestamp, not an offset, so new messages never shift the page under you.

// Shellcurl "https://meta.sanjusk.in/api/v1/messages?since=2026-09-01T10:00:00Z&direction=incoming" \
  -H "Authorization: Bearer mbsp_your_key_here"

# {
#   "success": true,
#   "data": [
#     { "id": "...", "from": "919876543210", "type": "text",
#       "text": "Where is my order?", "timestamp": "2026-09-01T10:05:00.000Z" }
#   ],
#   "nextSince": "2026-09-01T10:05:00.000Z",
#   "hasMore": false
# }

Query parameters: since (ISO timestamp), direction (incoming or outgoing), phone, and limit (up to 200). Messages come back oldest first.

Templates


The approved templates this key may send, so you do not have to copy names out of the dashboard by hand. Read-only on purpose: creating a template is a reviewed, consequential action that belongs behind a signed-in session rather than an automation key.

// Shellcurl https://meta.sanjusk.in/api/v1/templates \
  -H "Authorization: Bearer mbsp_your_key_here"

Errors & Limits


Every error is JSON with success: false, the operation that failed, and a human-readable message. Where Meta returned a code of its own it is passed through unchanged as code.

Rate limit

60 requests per minute per key for sending, 120 per minute for GET /api/v1/messages. Exceeding it returns 429 with a Retry-After header in seconds. The limit is per key, not per account, so one runaway integration cannot consume another's budget.

Status codes
StatusMeaningWhat to do
400
A required field is missing or malformedFix the request body; retrying will not help
401
No API key, or the key was revokedSend Authorization: Bearer <key>, or create a new key
403
OUTSIDE_24H_WINDOWThe 24-hour window has closed — send an approved template instead
409
No WhatsApp number connected for this keyConnect a number under Platform → Numbers
429
Rate limit exceededBack off for the seconds given in Retry-After
502
Meta rejected the request or was unreachableRead message and code; retry transient failures with backoff
503
The database is temporarily unavailableRetry with backoff
SK Digital Developer Documentation