🚀 GoSendAPI is in beta — Meta App Review in progress. Get on the waitlist →

Messages

Sending and receiving WhatsApp messages is what GoSendAPI is for. Everything goes through one endpoint:

POST /v1/messages

The type field determines the shape of the rest of the payload.

Supported types

TypeUse caseRequires template?
textPlain text messageOnly outside 24h window
templatePre-approved templateAlways (for outbound)
imageJPEG/PNG imageSame as text
videoMP4 videoSame as text
audioMP3/OGG audioSame as text
documentPDF/DOCX/XLSXSame as text
stickerWebP stickerSame as text
locationLat/lon + labelSame as text
interactiveButtons / listsSame as text
contactsvCard-style contact shareSame as text

Examples by type

text

curl https://cloud.gosendapi.com/v1/messages \
  -H "X-API-Key: gsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number_id": "555123456789",
    "to": "5491140123456",
    "type": "text",
    "text": {
      "body": "Hola Juan, llegamos al lugar đź‘‹"
    }
  }'

template

{
  "phone_number_id": "555123456789",
  "to": "5491140123456",
  "type": "template",
  "template": {
    "name": "appointment_reminder",
    "language": { "code": "es_AR" },
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "Juan" },
          { "type": "text", "text": "20/05" }
        ]
      }
    ]
  }
}

image / video / audio / document

Two ways to attach media:

Option A — hosted URL (we download and upload to Meta):

{
  "phone_number_id": "555123456789",
  "to": "5491140123456",
  "type": "image",
  "image": {
    "link": "https://your-cdn.com/path/to/image.jpg",
    "caption": "Order #1234 receipt"
  }
}

Option B — Meta media ID (if you uploaded to Meta separately, e.g. for reuse):

{
  "image": {
    "id": "934567890123456",
    "caption": "Same image, reused 100 times"
  }
}

For images sent once, use link. For images sent to many recipients (e.g. marketing campaigns), upload once via POST /v1/media, get an id, and reuse — saves bandwidth and is faster.

interactive (buttons + lists)

{
  "phone_number_id": "555123456789",
  "to": "5491140123456",
  "type": "interactive",
  "interactive": {
    "type": "button",
    "body": { "text": "¿Confirmás tu turno del 20/05?" },
    "action": {
      "buttons": [
        { "type": "reply", "reply": { "id": "confirm_yes", "title": "SĂ­, confirmo" } },
        { "type": "reply", "reply": { "id": "reschedule", "title": "Reprogramar" } }
      ]
    }
  }
}

When the user taps a button, you receive an inbound webhook with the id field — match it to your own action.

Response

A successful send returns:

{
  "id": "1234567890",
  "phone_number_id": "555123456789",
  "direction": "outbound",
  "message_type": "text",
  "wa_message_id": "wamid.HBgLNTQ5...",
  "status": "sent",
  "to_phone": "5491140123456",
  "created_at": "2026-05-18T12:34:56.789Z"
}
  • id: GoSendAPI’s internal ID (use for follow-up queries)
  • wa_message_id: Meta’s tracking ID (you’ll see it again in status webhook events)
  • status: starts as sent; updates to delivered, read, or failed async

Status updates

WhatsApp delivers async status updates. Either poll:

curl https://cloud.gosendapi.com/v1/messages/1234567890 \
  -H "X-API-Key: gsk_live_..."

Or subscribe to the whatsapp.message.delivered / whatsapp.message.read / whatsapp.message.failed webhook events (recommended).

Status lifecycle:

sent ──> delivered ──> read
  │
  └──> failed (with error code)

Listing messages

GET /v1/messages?phone_number_id=555123456789&direction=outbound&since=2026-05-01&page=1&per_page=50

Filters:

ParamTypeDescription
phone_number_idstringOnly messages on this number
directioninbound | outboundFilter by direction
statusstringsent/delivered/read/failed
sinceISO dateFrom this date onwards
untilISO dateUp to this date
pageintDefault 1
per_pageintDefault 20, max 100

Receiving messages (inbound)

Inbound messages arrive via webhook to your configured endpoint. Event: whatsapp.message.received.

Sample payload:

{
  "event": "whatsapp.message.received",
  "delivery_id": "8b3f...",
  "occurred_at": "2026-05-18T12:35:00Z",
  "tenant_id": "42",
  "phone_number_id": "555123456789",
  "message": {
    "id": "wamid.HBgLNTQ5...",
    "timestamp": "1747570500",
    "type": "text",
    "from": "5491140123456",
    "text": { "body": "Confirmo el turno" },
    "gosendapi": {
      "direction": "inbound",
      "status": "received",
      "origin": "cloud_api",
      "has_media": false,
      "content": "Confirmo el turno"
    }
  },
  "conversation": { "id": "...", "status": "active" },
  "is_new_conversation": false
}

See Webhooks guide for verification and retry policy.

Best practices

  • Always send idempotency_key in headers for retries. Same key = same message, won’t duplicate.
  • Reuse media via id for marketing sends — way faster than re-uploading
  • Pre-validate phone numbers (E164 format, no spaces, no +) before calling the API to reduce 400s
  • Cache phone_number_id → customer_id in your DB to avoid joining on every event handler
  • Don’t poll — use webhooks for status updates. Polling 1000s of messages is wasteful and gets rate-limited

What’s next