Skip to content

Send WhatsApp messages with AvenPing API

All /v2/messages routes send through your linked WhatsApp line. Provide phoneNumberId plus to (phone) or recipient (BSUID). Successful sends also upsert the contact in AvenPing.

Base: /v2/messages

All routes require auth, plan, Premium/Enterprise, and phoneNumberId ownership — see overview and Account.

Also read: HTTP API v2 Overview

Prerequisites

  • An API token and Premium/Enterprise plan (Authentication)
  • A phoneNumberId from GET /v2/me
  • A destination phone (to) or Business-Scoped User ID (recipient)

Step-by-step

  1. Authenticate
    Send Authorization: Bearer YOUR_TOKEN or X-API-Token: YOUR_TOKEN.

  2. Set phoneNumberId
    Use a Meta Phone number ID that belongs to a linked WABA. Missing: 400 VALIDATION_ERROR. Not owned: 403 FORBIDDEN.

  3. Choose to or recipient
    Provide to (E.164-like phone; non-digits stripped) or recipient (BSUID, CC.alphanumeric or CC.ENT.alphanumeric). When both are sent, Meta prefers the phone. Authentication (OTP) templates require to (Meta error 131062 for BSUID-only auth sends).

  4. POST the message body
    Use a dedicated path (/send/text, /send/media, …) or smart POST /v2/messages/send. Zod failures return 400 VALIDATION_ERROR with details. A blocked contact returns 403 FORBIDDEN and does not call Meta. Graph 4xx (other than 401/403/404) map to VALIDATION_ERROR with Meta’s payload in details.


Common POST fields

FieldTypeRequiredNotes
phoneNumberIdstringyesMeta phone number ID
tostringone ofE.164 digits (phone). Use when the contact’s phone is known
recipientstringone ofBusiness-Scoped User ID (BSUID), format CC.alphanumeric or CC.ENT.alphanumeric
contextMessageWamidstringnoReply context

Provide to or recipient. When both are sent, Meta prefers the phone (to). Authentication (OTP) templates require a phone and cannot be sent to BSUID-only contacts (Meta error 131062).


BSUID (Business-Scoped User ID)

Meta assigns a portfolio-scoped BSUID to each WhatsApp user. It appears in webhooks as user_id, from_user_id, or recipient_user_id. When a user adopts a username, their phone may be omitted from webhooks; use recipient for outbound sends in that case.

Webhook regression matrix (see scripts/fixtures/bsuid-webhooks/README.md in the main app repo):

  • Inbound BSUID-only message creates a thread
  • Status webhook with recipient_user_id resolves the recipient
  • user_id_update merges identity without duplicate contacts
  • Outbound BSUID-only send uses recipient
  • Phone + BSUID merge on one contact row
  • Auth template blocked for BSUID-only contacts

Typical success data (Meta send message)

All POST .../messages/... JSON endpoints wrap Meta’s response in { success: true, data }. A typical successful data value:

json
{
  "messaging_product": "whatsapp",
  "contacts": [
    {
      "input": "15551234567",
      "wa_id": "15551234567"
    }
  ],
  "messages": [
    {
      "id": "wamid.HBgL..."
    }
  ]
}

On failure, Meta Graph errors are mapped to the same envelope as other routes: 4xx usually VALIDATION_ERROR (or 401/403/404), with Meta’s payload in details when present. Transport failures use 500/502 INTERNAL_ERROR.

Successful sends upsert the contact and inbox thread in AvenPing so the message appears in the dashboard. If that WhatsApp user is blocked on the line, the API returns 403 FORBIDDEN (Contact is blocked on this WhatsApp line) and does not call Meta.


POST /v2/messages/send/text

Body

FieldTypeRequired
textstringyes

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/text" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "text": "Hello from the API"
  }'

Response 200: envelope with data as in Typical success data (Meta send message).


POST /v2/messages/send/media

Body — media

FieldTypeRequiredNotes
typeenumyesimage, video, audio, document
mediaIdstringyesMeta media id
captionstringnoNot used for audio in payload
filenamestringnoFor document

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/media" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "media": {
      "type": "image",
      "mediaId": "1234567890",
      "caption": "Check this out"
    }
  }'

Response 200: same Meta send shape as above.


POST /v2/messages/send/location

Body — location

FieldTypeRequired
latitudestring | numberyes
longitudestring | numberyes
namestringno
addressstringno

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/location" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "location": {
      "latitude": 37.422,
      "longitude": -122.084,
      "name": "HQ",
      "address": "1600 Amphitheatre Parkway"
    }
  }'

Response 200: same Meta send shape.


POST /v2/messages/send/interactive/catalog-message

FieldTypeRequired
bodyTextstringyes
footerTextstringno
thumbnailProductRetailerIdstringno

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/interactive/catalog-message" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "bodyText": "Browse our catalog",
    "footerText": "Tap below"
  }'

Response 200: same Meta send shape.


POST /v2/messages/send/interactive/product

FieldTypeRequired
catalogIdstringyes
productRetailerIdstringyes
bodyTextstringno
footerTextstringno

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/interactive/product" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "catalogId": "CATALOG_ID",
    "productRetailerId": "SKU-001",
    "bodyText": "Featured item"
  }'

Response 200: same Meta send shape.


POST /v2/messages/send/interactive/product-list

FieldTypeRequired
catalogIdstringyes
headerTextstringyes
bodyTextstringyes
footerTextstringno
sectionsarrayyes (min 1)

Each section: { "title": string, "product_items": [ { "product_retailer_id": string } ] } (at least one product per section).

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/interactive/product-list" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "catalogId": "CATALOG_ID",
    "headerText": "Shop",
    "bodyText": "Pick a product",
    "sections": [
      {
        "title": "Featured",
        "product_items": [{ "product_retailer_id": "SKU-001" }]
      }
    ]
  }'

Response 200: same Meta send shape.


POST /v2/messages/send/interactive/product-carousel

FieldTypeRequired
bodyTextstringyes
cardsarrayyes (min 2 cards)

Each card: { "catalogId": string, "productRetailerId": string }.

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/interactive/product-carousel" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "bodyText": "Swipe to browse",
    "cards": [
      { "catalogId": "CATALOG_ID", "productRetailerId": "SKU-001" },
      { "catalogId": "CATALOG_ID", "productRetailerId": "SKU-002" }
    ]
  }'

Response 200: same Meta send shape.


POST /v2/messages/send/interactive/button

FieldTypeRequired
bodyTextstringyes
buttonsarrayyes, 1–3 items

Each button: { "id": string, "title": string } → sent as reply buttons.

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/interactive/button" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "bodyText": "Choose one",
    "buttons": [
      { "id": "yes", "title": "Yes" },
      { "id": "no", "title": "No" }
    ]
  }'

Response 200: same Meta send shape.


POST /v2/messages/send/interactive/list

FieldTypeRequired
bodyTextstringyes
buttonTextstringyes
headerTextstringno
footerTextstringno
sectionsarrayyes (min 1)

Each section: title + rows (min 1). Each row: id, title, optional description.

Example request:

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send/interactive/list" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "bodyText": "Select an option",
    "buttonText": "Open menu",
    "headerText": "Menu",
    "sections": [
      {
        "title": "Options",
        "rows": [
          { "id": "1", "title": "Option A", "description": "Details" }
        ]
      }
    ]
  }'

Response 200: same Meta send shape.


POST /v2/messages/send (smart dispatcher)

Single endpoint that infers message type from the JSON body (same base fields + optional contextMessageWamid):

ConditionDispatches to
text is a stringText send
media is an objectMedia send
location is an objectLocation send
bodyText + catalogId + productRetailerIdSingle product (note: bodyText must be present as string for this branch)
bodyText + cards arrayProduct carousel
bodyText + catalogId + sections arrayProduct list
bodyText + (thumbnailProductRetailerId or footerText)Catalog message
bodyText + buttons arrayReply buttons
bodyText + buttonText + sections arrayList message

Example request (text via smart send):

bash
curl -sS -X POST "https://api.avenping.com/v2/messages/send" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "109876543210987",
    "to": "15551234567",
    "text": "Hello via smart send"
  }'

Response 200: same Meta send shape as dedicated text route.

Response 400 when no branch matches:

json
{
  "success": false,
  "error": "VALIDATION_ERROR",
  "message": "Unsupported message payload."
}

For interactive types, prefer the explicit path URLs above when documenting examples; use /send when you want one integration point.


FAQ

Should I use to or recipient?

Use to (phone) when you have the number. Use recipient (BSUID) when webhooks omit the phone (for example after a username). Authentication (OTP) templates require to. When both are sent, Meta prefers the phone.

What happens if the contact is blocked?

The API returns 403 FORBIDDEN (Contact is blocked on this WhatsApp line) and does not call Meta. Successful sends upsert the contact and inbox thread in AvenPing.

How are validation and Graph errors returned?

Zod schema failures return 400 VALIDATION_ERROR with details set to the issue list. Smart send returns 400 with message Unsupported message payload. when no branch matches. Meta Graph 401/403/404 keep those statuses; other Graph 4xx map to VALIDATION_ERROR with the Graph payload in details. Transport failures use 500/502 INTERNAL_ERROR.

Does a successful send appear in the AvenPing inbox?

Yes. Successful sends upsert the contact and inbox thread so the message appears in the dashboard.