Contents

Messaging

Sending messages

Every message is one POST /v1/accounts/{AC}/messages. The type field says what kind of message it is, and a block of the same name carries its content.

The request

FieldMeaning
fromYour sender’s SID (WA…). Or give senderPoolSid to let ShipMsg pick one from a pool.
toThe recipient in international format, +2250700000000. National numbers work with defaultCountry. Or address a saved contact with contactSid.
typeOne of the types below.
metadataUp to 16 of your own string values (order IDs, user IDs), 512 characters each. Echoed on every event for this message.
sendAtSchedule for later (ISO 8601).
validityPeriodSecondsGive up if the message cannot be sent within this time; it then ends as expired.
statusCallbackUrlAlso send this message’s statuses to a verified endpoint of yours.

The answer is 202 Accepted with the message in status queued. Its progress — sent, delivered, read, or failed with a reason — arrives as events.

A complete template send in each language:

curl -X POST "https://api.shipmsg.com/v1/accounts/$SHIPMESSAGES_ACCOUNT_SID/messages" \
  -H "Authorization: Bearer $SHIPMESSAGES_API_KEY" \
  -H "ShipMessages-Version: 2026-08-21" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "from": "'"$SHIPMESSAGES_SANDBOX_SENDER_SID"'",
  "to": "+15550000001",
  "type": "template",
  "template": {
    "name": "order_ready",
    "language": "en_US",
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "Ada"
          },
          {
            "type": "text",
            "text": "A-1042"
          }
        ]
      }
    ]
  }
}'

The 24-hour service window

WhatsApp only lets a business send free-form messages (text, media, interactive …) within 24 hours of the customer’s last message to it. Outside that window, send an approved template; anything else is refused with SERVICE_WINDOW_CLOSED. Inbound messages carry serviceWindowExpiresAt so you know how long you have.

Message types

Text

Up to 4,096 characters. Set previewUrl to show a link preview.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "text",
  "text": {
    "body": "Your table for 2 is booked for 8 pm. Reply CHANGE to move it.",
    "previewUrl": false
  }
}

Template

The only type you can send outside the 24-hour service window. Parameters fill the template’s {{1}}, {{2}} … in order (or by name for named templates).

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "template",
  "template": {
    "name": "order_ready",
    "language": "en_US",
    "components": [
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "Ada"
          },
          {
            "type": "text",
            "text": "A-1042"
          }
        ]
      }
    ]
  }
}

Image, video, audio, document and sticker

Upload the file first (below), then send it by its ME… SID. The same media block serves every media type; caption is ignored for audio and stickers; voice: true sends audio as a voice note.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "image",
  "media": {
    "sid": "ME…",
    "caption": "Your receipt"
  }
}

Document with a file name

The file name is what the customer sees in WhatsApp.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "document",
  "media": {
    "sid": "ME…",
    "filename": "invoice-1042.pdf",
    "caption": "Invoice 1042"
  }
}

Location

Latitude and longitude are required; name and address are shown under the pin.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "location",
  "location": {
    "latitude": 5.3364,
    "longitude": -4.0267,
    "name": "Our shop",
    "address": "Rue des Jardins, Abidjan"
  }
}

Contact card

Up to 10 cards; formattedName is required on each.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "contacts",
  "contacts": {
    "contacts": [
      {
        "formattedName": "ShipMsg Support",
        "phones": [
          {
            "value": "+15550000001",
            "type": "WORK"
          }
        ]
      }
    ]
  }
}

Reaction

React to a message the customer sent, by its WhatsApp message ID. An empty emoji removes the reaction.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "reaction",
  "reaction": {
    "targetMessageId": "wamid.…",
    "emoji": "👍"
  }
}

Reply buttons

Up to 3 buttons of up to 20 characters. The customer’s choice arrives as an inbound message carrying the button id.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "interactive",
  "interactive": {
    "subtype": "button",
    "body": "Is 8 pm still good for you?",
    "buttons": [
      {
        "id": "keep",
        "title": "Yes, keep it"
      },
      {
        "id": "move",
        "title": "Change time"
      }
    ]
  }
}

List

Up to 10 sections and 10 rows in total behind one button.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "interactive",
  "interactive": {
    "subtype": "list",
    "body": "Which day suits you?",
    "button": "Choose a day",
    "sections": [
      {
        "title": "This week",
        "rows": [
          {
            "id": "thu",
            "title": "Thursday"
          },
          {
            "id": "fri",
            "title": "Friday",
            "description": "Busy after 7 pm"
          }
        ]
      }
    ]
  }
}

Link button

One button that opens a URL.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "interactive",
  "interactive": {
    "subtype": "cta_url",
    "body": "Track your parcel",
    "displayText": "Open tracking",
    "url": "https://example.com/track/1042"
  }
}

Location request

Asks the customer to share their location; the answer arrives as an inbound location message.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "interactive",
  "interactive": {
    "subtype": "location_request",
    "body": "Where should we deliver?"
  }
}

Flow

Opens a published WhatsApp Flow (a form inside WhatsApp).

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "flow",
  "flow": {
    "flowSid": "FL…",
    "body": "Book your appointment",
    "ctaText": "Book now",
    "action": "navigate",
    "screen": "WELCOME"
  }
}

Catalog, product and product list

Need a Meta commerce catalog connected to the WhatsApp Business Account. Types: catalog, product, product_list, carousel.

request body
{
  "from": "WA…",
  "to": "+15550000001",
  "type": "catalog",
  "catalog": {
    "catalogId": "1234567890",
    "body": "Browse this week’s menu",
    "thumbnailProductRetailerId": "sku-42"
  }
}

Uploading media

Upload a file as multipart form data (field file), or ask ShipMsg to fetch it from a public HTTPS URL. Every file is virus-scanned; send it once its state is READY (sending earlier answers MEDIA_NOT_READY, which is safe to retry).

curl
curl -X POST "https://api.shipmsg.com/v1/accounts/$SHIPMESSAGES_ACCOUNT_SID/media" \
  -H "Authorization: Bearer $SHIPMESSAGES_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@receipt.jpg"

# or
curl -X POST "https://api.shipmsg.com/v1/accounts/$SHIPMESSAGES_ACCOUNT_SID/media" \
  -H "Authorization: Bearer $SHIPMESSAGES_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/receipt.jpg"}'

Message statuses

StatusMeaning
queuedAccepted by ShipMsg, waiting to be sent (see queuedReason when it is held back on purpose).
scheduledWaiting for its sendAt time.
sendingBeing handed to WhatsApp.
sentWhatsApp accepted it.
deliveredIt reached the customer’s phone.
read / playedThe customer opened it (or played a voice note), if they share read receipts.
failedIt will not be delivered; error says why.
canceled / expiredYou canceled it, or its validity period ran out before it could be sent.
unknownWhatsApp has not said what happened. ShipMsg never guesses “delivered”.