Messages
Sending and receiving WhatsApp messages is what GoSendAPI is for. Everything goes through one endpoint:
POST /v1/messagesThe type field determines the shape of the rest of the payload.
Supported types
| Type | Use case | Requires template? |
|---|---|---|
text | Plain text message | Only outside 24h window |
template | Pre-approved template | Always (for outbound) |
image | JPEG/PNG image | Same as text |
video | MP4 video | Same as text |
audio | MP3/OGG audio | Same as text |
document | PDF/DOCX/XLSX | Same as text |
sticker | WebP sticker | Same as text |
location | Lat/lon + label | Same as text |
interactive | Buttons / lists | Same as text |
contacts | vCard-style contact share | Same 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 assent; updates todelivered,read, orfailedasync
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=50Filters:
| Param | Type | Description |
|---|---|---|
phone_number_id | string | Only messages on this number |
direction | inbound | outbound | Filter by direction |
status | string | sent/delivered/read/failed |
since | ISO date | From this date onwards |
until | ISO date | Up to this date |
page | int | Default 1 |
per_page | int | Default 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_keyin headers for retries. Same key = same message, won’t duplicate. - Reuse media via
idfor 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_idin 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