Docs

Sending messages

One endpoint sends every WhatsApp message type. Messages are queued, paced to WhatsApp's limits and tracked through webhooks.

POST/v1/messages— Send one message
POST/v1/messages/bulk— Send up to 1,000 messages
GET/v1/messages/{id}— Retrieve a message and its status

Request body

Parameters
  • tostringoptional
    Recipient phone in E.164, with or without +. One of to, recipient or contact_id is required.
  • recipientstringoptional
    WhatsApp business-scoped user id (BSUID), for users who hide their number.
  • contact_iduuidoptional
    An Engage contact id.
  • phone_number_iduuidoptional
    Engage id of the sending number. Defaults to your organization's default number.
  • typeenumrequired
    text · template · image · video · audio · document · sticker · location · contacts · interactive · reaction
  • {type}objectrequired
    An object named after the type with its content, e.g. text, image.
  • reply_touuidoptional
    Engage message id to quote as a reply.
  • client_refstringoptional
    Your own reference, echoed in status webhooks.

The response is 202 Accepted with { "id": "…", "status": "queued" }. Status then moves through sent → delivered → read, or ends in failed with an error object.

Customer service window
Every type except template requires an open 24-hour window — i.e. the customer messaged you in the last 24 hours.

Text

Up to 4,096 characters. WhatsApp formatting is supported: *bold*, _italic_, ~strike~, ```mono```. Set preview_url to render a link preview.

{
  "to": "+919812345678",
  "type": "text",
  "text": {
    "body": "Hi Ananya! Your order *#48213* has shipped 🚚 Track it: https://acme.in/t/48213",
    "preview_url": true
  }
}

Template

Templates can be sent at any time. Reference them by name + language or by Engage template_id. Fill parameters with the simple variables map — keys are the placeholder names (1, 2… for positional templates, or the parameter name for named ones), prefixed with the component when needed: header.1, button.0.

Template with variables
{
  "to": "+919812345678",
  "type": "template",
  "template": {
    "name": "order_shipped",
    "language": "en",
    "variables": {
      "1": "Ananya",
      "2": "#48213",
      "header.1": "https://cdn.acme.in/orders/48213.jpg",
      "button.0": "48213"
    }
  }
}

Prefer full control? Pass Meta's components array instead of variables and Engage forwards it unchanged.

Media

Send image, video, audio, document or sticker with either an Engage media_id (from POST /v1/media) or a public HTTPS link.

TypeFormatsMax size
imageJPEG, PNG5 MB
videoMP4, 3GPP (H.264 + AAC)16 MB
audioAAC, MP3, M4A, AMR, OGG (Opus)16 MB
documentPDF, DOC(X), XLS(X), PPT(X), TXT100 MB
stickerWebP (static 100 KB, animated 500 KB)500 KB
{
  "to": "+919812345678",
  "type": "image",
  "image": {
    "link": "https://cdn.acme.in/sale.jpg",
    "caption": "Festive sale — 30% off today only"
  }
}

Uploading media

POST /v1/media (multipart)
curl https://api.engage.swastik.ai/v1/media \
  -H "Authorization: Bearer $ENGAGE_API_KEY" \
  -F "file=@./brochure.pdf"

# → { "id": "6a1f0c2e-…", "kind": "document", "mime": "application/pdf",
#     "size_bytes": 482113, "filename": "brochure.pdf", "url": "…" }

Location

json
{
  "to": "+919812345678",
  "type": "location",
  "location": {
    "latitude": 12.9716,
    "longitude": 77.5946,
    "name": "Acme Store — Indiranagar",
    "address": "100 Feet Rd, Bengaluru 560038"
  }
}

Contacts

json
{
  "to": "+919812345678",
  "type": "contacts",
  "contacts": [
    {
      "name": {
        "formatted_name": "Acme Support",
        "first_name": "Acme"
      },
      "phones": [
        {
          "phone": "+918045671234",
          "type": "WORK"
        }
      ],
      "emails": [
        {
          "email": "support@acme.in",
          "type": "WORK"
        }
      ]
    }
  ]
}

The contacts array follows Meta's contact object schema.

Interactive

Pass Meta's interactive object as-is. Replies arrive as message.received webhooks with the chosen button or row id.

{
  "to": "+919812345678",
  "type": "interactive",
  "interactive": {
    "type": "button",
    "body": {
      "text": "Confirm your delivery slot for Saturday, 9–11 AM?"
    },
    "action": {
      "buttons": [
        {
          "type": "reply",
          "reply": {
            "id": "slot_confirm",
            "title": "Confirm"
          }
        },
        {
          "type": "reply",
          "reply": {
            "id": "slot_change",
            "title": "Change slot"
          }
        }
      ]
    }
  }
}

Reaction

React to a message you received. Send an empty emoji to remove the reaction.

json
{
  "to": "+919812345678",
  "type": "reaction",
  "reaction": {
    "message_id": "8b2e3f1a-…",
    "emoji": "👍"
  }
}

Replying to a message

Add reply_to with the Engage id of an inbound message to show it as a quoted reply.

json
{
  "to": "+919812345678",
  "type": "text",
  "reply_to": "8b2e3f1a-…",
  "text": {
    "body": "Yes — the blue variant is back in stock!"
  }
}

Bulk send

Send up to 1,000 messages per request. Each item is validated independently; the response lists results in the same order.

POST /v1/messages/bulk
{
  "messages": [
    { "to": "+919812345678", "type": "template", "template": { "name": "order_shipped", "language": "en", "variables": { "1": "Ananya", "2": "#48213" } } },
    { "to": "+91000", "type": "text", "text": { "body": "Hi" } }
  ]
}

// → 200
{
  "data": [
    { "id": "0f9b1c7e-…", "status": "queued" },
    { "error": { "code": "validation_failed", "message": "to must be a valid phone number" } }
  ]
}

For large audiences, prefer campaigns — they handle audience expansion, opt-outs and reporting for you.