> ## Documentation Index
> Fetch the complete documentation index at: https://developer.zapsterapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending WhatsApp messages with WABA

> How to send text, media, buttons, and templates through an official (WABA) instance using the Zapster API. Examples in cURL, Node.js, Python, and Go.

This guide shows how to send messages through an official (WABA) instance. The endpoint is the same as for unofficial instances, `POST /v1/wa/messages`, with the same request signature. Zapster converts your payload into Meta's Cloud API format automatically.

## Before you start

1. You need a [connected WABA instance](/en/v1/guides/connect-waba-instance).
2. Free messages (text, media, buttons) are only delivered inside the **24-hour conversation window**, which opens when the customer messages your number. Outside the window, use a **template** approved by Meta.
3. WABA instances do not send to groups. If you need groups, use an unofficial instance. See the comparison in [WABA vs unofficial](/en/v1/guides/waba-vs-unofficial).

In every example below, replace `YOUR_API_TOKEN` with your API token and `YOUR_INSTANCE_ID` with the WABA instance ID.

<Note>
  Free messages (text, media, and buttons) sent through an official instance are free until **September 30, 2026**. Starting **October 1, 2026**, Meta begins charging for them. Learn what changes in [WhatsApp official (WABA) message pricing](/en/v1/concepts/message-pricing).
</Note>

## Plain text

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.zapsterapi.com/v1/wa/messages \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "X-Instance-ID: YOUR_INSTANCE_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "recipient": "5511999999999",
      "text": "Hi! Your order is confirmed."
    }'
  ```

  ```javascript Node.js (axios) theme={null}
  const axios = require('axios')

  const response = await axios.post(
    'https://api.zapsterapi.com/v1/wa/messages',
    {
      recipient: '5511999999999',
      text: 'Hi! Your order is confirmed.',
    },
    {
      headers: {
        Authorization: 'Bearer YOUR_API_TOKEN',
        'X-Instance-ID': 'YOUR_INSTANCE_ID',
      },
    },
  )

  console.log(response.data.message_id)
  ```

  ```javascript Node.js (fetch) theme={null}
  const response = await fetch('https://api.zapsterapi.com/v1/wa/messages', {
    body: JSON.stringify({
      recipient: '5511999999999',
      text: 'Hi! Your order is confirmed.',
    }),
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
      'X-Instance-ID': 'YOUR_INSTANCE_ID',
    },
    method: 'POST',
  })

  const data = await response.json()
  console.log(data.message_id)
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.zapsterapi.com/v1/wa/messages",
      headers={
          "Authorization": "Bearer YOUR_API_TOKEN",
          "X-Instance-ID": "YOUR_INSTANCE_ID",
      },
      json={
          "recipient": "5511999999999",
          "text": "Hi! Your order is confirmed.",
      },
  )

  print(response.json()["message_id"])
  ```

  ```go Go theme={null}
  package main

  import (
  	"fmt"
  	"io"
  	"net/http"
  	"strings"
  )

  func main() {
  	body := strings.NewReader(`{
  		"recipient": "5511999999999",
  		"text": "Hi! Your order is confirmed."
  	}`)

  	req, _ := http.NewRequest("POST", "https://api.zapsterapi.com/v1/wa/messages", body)
  	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
  	req.Header.Set("X-Instance-ID", "YOUR_INSTANCE_ID")
  	req.Header.Set("Content-Type", "application/json")

  	res, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	defer res.Body.Close()

  	data, _ := io.ReadAll(res.Body)
  	fmt.Println(string(data))
  }
  ```
</CodeGroup>

## Media (image, video, or document)

Send the media by public URL or base64 in the `media` field. The type (image, video, document) is detected automatically from the file. For documents, you can set `fileName` to control the name shown in WhatsApp.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.zapsterapi.com/v1/wa/messages \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "X-Instance-ID: YOUR_INSTANCE_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "recipient": "5511999999999",
      "media": {
        "url": "https://example.com/image.jpg",
        "caption": "Check out the July catalog"
      }
    }'
  ```

  ```javascript Node.js (axios) theme={null}
  const axios = require('axios')

  await axios.post(
    'https://api.zapsterapi.com/v1/wa/messages',
    {
      media: {
        caption: 'Check out the July catalog',
        url: 'https://example.com/image.jpg',
      },
      recipient: '5511999999999',
    },
    {
      headers: {
        Authorization: 'Bearer YOUR_API_TOKEN',
        'X-Instance-ID': 'YOUR_INSTANCE_ID',
      },
    },
  )
  ```

  ```javascript Node.js (fetch) theme={null}
  await fetch('https://api.zapsterapi.com/v1/wa/messages', {
    body: JSON.stringify({
      media: {
        caption: 'Check out the July catalog',
        url: 'https://example.com/image.jpg',
      },
      recipient: '5511999999999',
    }),
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
      'X-Instance-ID': 'YOUR_INSTANCE_ID',
    },
    method: 'POST',
  })
  ```

  ```python Python theme={null}
  import requests

  requests.post(
      "https://api.zapsterapi.com/v1/wa/messages",
      headers={
          "Authorization": "Bearer YOUR_API_TOKEN",
          "X-Instance-ID": "YOUR_INSTANCE_ID",
      },
      json={
          "recipient": "5511999999999",
          "media": {
              "url": "https://example.com/image.jpg",
              "caption": "Check out the July catalog",
          },
      },
  )
  ```

  ```go Go theme={null}
  package main

  import (
  	"net/http"
  	"strings"
  )

  func main() {
  	body := strings.NewReader(`{
  		"recipient": "5511999999999",
  		"media": {
  			"url": "https://example.com/image.jpg",
  			"caption": "Check out the July catalog"
  		}
  	}`)

  	req, _ := http.NewRequest("POST", "https://api.zapsterapi.com/v1/wa/messages", body)
  	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
  	req.Header.Set("X-Instance-ID", "YOUR_INSTANCE_ID")
  	req.Header.Set("Content-Type", "application/json")

  	res, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	res.Body.Close()
  }
  ```
</CodeGroup>

To send a video or document, just change the URL. Example of a document with a file name:

```json theme={null}
{
  "recipient": "5511999999999",
  "media": {
    "url": "https://example.com/contract.pdf",
    "fileName": "contract.pdf",
    "caption": "Here is the contract for signature"
  }
}
```

## Interactive buttons

On WABA, buttons become Cloud API interactive messages and follow Meta's rules: up to 3 `reply` buttons, or exactly 1 `url` button, without mixing the two types. The `call` and `copyable` buttons do not exist in session messages (see [Common errors](#common-errors)). The full matrix is in [Messages with buttons](/en/v1/guides/messages-with-buttons).

When you send `media` together with buttons, the media becomes the header of the interactive message (image, video, or document; audio is not supported). If you send `media.caption` and `text` together, `caption` takes priority and becomes the message body.

### Quick reply buttons with an image

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.zapsterapi.com/v1/wa/messages \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "X-Instance-ID: YOUR_INSTANCE_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "recipient": "5511999999999",
      "text": "Would you like to confirm tomorrow'\''s appointment?",
      "media": {
        "url": "https://example.com/clinic.jpg"
      },
      "buttons": [
        { "id": "confirm", "label": "Confirm", "type": "reply" },
        { "id": "reschedule", "label": "Reschedule", "type": "reply" },
        { "id": "cancel", "label": "Cancel", "type": "reply" }
      ]
    }'
  ```

  ```javascript Node.js (axios) theme={null}
  const axios = require('axios')

  await axios.post(
    'https://api.zapsterapi.com/v1/wa/messages',
    {
      buttons: [
        { id: 'confirm', label: 'Confirm', type: 'reply' },
        { id: 'reschedule', label: 'Reschedule', type: 'reply' },
        { id: 'cancel', label: 'Cancel', type: 'reply' },
      ],
      media: { url: 'https://example.com/clinic.jpg' },
      recipient: '5511999999999',
      text: 'Would you like to confirm tomorrow\'s appointment?',
    },
    {
      headers: {
        Authorization: 'Bearer YOUR_API_TOKEN',
        'X-Instance-ID': 'YOUR_INSTANCE_ID',
      },
    },
  )
  ```

  ```javascript Node.js (fetch) theme={null}
  await fetch('https://api.zapsterapi.com/v1/wa/messages', {
    body: JSON.stringify({
      buttons: [
        { id: 'confirm', label: 'Confirm', type: 'reply' },
        { id: 'reschedule', label: 'Reschedule', type: 'reply' },
        { id: 'cancel', label: 'Cancel', type: 'reply' },
      ],
      media: { url: 'https://example.com/clinic.jpg' },
      recipient: '5511999999999',
      text: 'Would you like to confirm tomorrow\'s appointment?',
    }),
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
      'X-Instance-ID': 'YOUR_INSTANCE_ID',
    },
    method: 'POST',
  })
  ```

  ```python Python theme={null}
  import requests

  requests.post(
      "https://api.zapsterapi.com/v1/wa/messages",
      headers={
          "Authorization": "Bearer YOUR_API_TOKEN",
          "X-Instance-ID": "YOUR_INSTANCE_ID",
      },
      json={
          "recipient": "5511999999999",
          "text": "Would you like to confirm tomorrow's appointment?",
          "media": {"url": "https://example.com/clinic.jpg"},
          "buttons": [
              {"id": "confirm", "label": "Confirm", "type": "reply"},
              {"id": "reschedule", "label": "Reschedule", "type": "reply"},
              {"id": "cancel", "label": "Cancel", "type": "reply"},
          ],
      },
  )
  ```

  ```go Go theme={null}
  package main

  import (
  	"net/http"
  	"strings"
  )

  func main() {
  	body := strings.NewReader(`{
  		"recipient": "5511999999999",
  		"text": "Would you like to confirm tomorrow's appointment?",
  		"media": { "url": "https://example.com/clinic.jpg" },
  		"buttons": [
  			{ "id": "confirm", "label": "Confirm", "type": "reply" },
  			{ "id": "reschedule", "label": "Reschedule", "type": "reply" },
  			{ "id": "cancel", "label": "Cancel", "type": "reply" }
  		]
  	}`)

  	req, _ := http.NewRequest("POST", "https://api.zapsterapi.com/v1/wa/messages", body)
  	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
  	req.Header.Set("X-Instance-ID", "YOUR_INSTANCE_ID")
  	req.Header.Set("Content-Type", "application/json")

  	res, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	res.Body.Close()
  }
  ```
</CodeGroup>

### Link button (url) with an image

Remember: on WABA there can only be 1 `url` button in the message, and it cannot be combined with `reply` buttons.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.zapsterapi.com/v1/wa/messages \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "X-Instance-ID: YOUR_INSTANCE_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "recipient": "5511999999999",
      "text": "Your July invoice is available.",
      "media": {
        "url": "https://example.com/invoice.jpg"
      },
      "buttons": [
        {
          "label": "View invoice",
          "type": "url",
          "url": "https://example.com/invoice/123"
        }
      ]
    }'
  ```

  ```javascript Node.js (axios) theme={null}
  const axios = require('axios')

  await axios.post(
    'https://api.zapsterapi.com/v1/wa/messages',
    {
      buttons: [
        {
          label: 'View invoice',
          type: 'url',
          url: 'https://example.com/invoice/123',
        },
      ],
      media: { url: 'https://example.com/invoice.jpg' },
      recipient: '5511999999999',
      text: 'Your July invoice is available.',
    },
    {
      headers: {
        Authorization: 'Bearer YOUR_API_TOKEN',
        'X-Instance-ID': 'YOUR_INSTANCE_ID',
      },
    },
  )
  ```

  ```javascript Node.js (fetch) theme={null}
  await fetch('https://api.zapsterapi.com/v1/wa/messages', {
    body: JSON.stringify({
      buttons: [
        {
          label: 'View invoice',
          type: 'url',
          url: 'https://example.com/invoice/123',
        },
      ],
      media: { url: 'https://example.com/invoice.jpg' },
      recipient: '5511999999999',
      text: 'Your July invoice is available.',
    }),
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
      'X-Instance-ID': 'YOUR_INSTANCE_ID',
    },
    method: 'POST',
  })
  ```

  ```python Python theme={null}
  import requests

  requests.post(
      "https://api.zapsterapi.com/v1/wa/messages",
      headers={
          "Authorization": "Bearer YOUR_API_TOKEN",
          "X-Instance-ID": "YOUR_INSTANCE_ID",
      },
      json={
          "recipient": "5511999999999",
          "text": "Your July invoice is available.",
          "media": {"url": "https://example.com/invoice.jpg"},
          "buttons": [
              {
                  "label": "View invoice",
                  "type": "url",
                  "url": "https://example.com/invoice/123",
              }
          ],
      },
  )
  ```

  ```go Go theme={null}
  package main

  import (
  	"net/http"
  	"strings"
  )

  func main() {
  	body := strings.NewReader(`{
  		"recipient": "5511999999999",
  		"text": "Your July invoice is available.",
  		"media": { "url": "https://example.com/invoice.jpg" },
  		"buttons": [
  			{ "label": "View invoice", "type": "url", "url": "https://example.com/invoice/123" }
  		]
  	}`)

  	req, _ := http.NewRequest("POST", "https://api.zapsterapi.com/v1/wa/messages", body)
  	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
  	req.Header.Set("X-Instance-ID", "YOUR_INSTANCE_ID")
  	req.Header.Set("Content-Type", "application/json")

  	res, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	res.Body.Close()
  }
  ```
</CodeGroup>

## Template (outside the 24h window)

Templates must be created and approved with Meta before sending. The `template` field is mutually exclusive with `text` and `media`.

<Info>
  The `template` object is a **mirror of Meta's format**. Zapster only wraps `name` and `language` (which becomes `{ "code": ... }`) and passes the `components` array exactly as you send it to the Cloud API, without reinterpreting it. So use the same format as Meta's Cloud API template documentation.
</Info>

`components` describes the parts of the template that receive values at send time:

* **`header`**: a header with media (image, video, or document) or with a text variable.
* **`body`**: the body, where the variables of the approved text go.
* **`button`**: the buttons that carry a dynamic value, such as the `payload` of a quick reply or the suffix of a URL.

Template variables can be **positional** (`{{1}}`, `{{2}}`, filled by the order of the `parameters` array) or **named** (`{{customer_name}}`, filled by `parameter_name`). Each template uses one style or the other, never both at once. The values you send must match what the approved template expects.

### Simple example (positional variable)

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.zapsterapi.com/v1/wa/messages \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "X-Instance-ID: YOUR_INSTANCE_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "recipient": "5511999999999",
      "template": {
        "name": "order_confirmation",
        "language": "en_US",
        "components": [
          {
            "type": "body",
            "parameters": [
              { "type": "text", "text": "John" }
            ]
          }
        ]
      }
    }'
  ```

  ```javascript Node.js (axios) theme={null}
  const axios = require('axios')

  await axios.post(
    'https://api.zapsterapi.com/v1/wa/messages',
    {
      recipient: '5511999999999',
      template: {
        components: [
          {
            parameters: [{ text: 'John', type: 'text' }],
            type: 'body',
          },
        ],
        language: 'en_US',
        name: 'order_confirmation',
      },
    },
    {
      headers: {
        Authorization: 'Bearer YOUR_API_TOKEN',
        'X-Instance-ID': 'YOUR_INSTANCE_ID',
      },
    },
  )
  ```

  ```javascript Node.js (fetch) theme={null}
  await fetch('https://api.zapsterapi.com/v1/wa/messages', {
    body: JSON.stringify({
      recipient: '5511999999999',
      template: {
        components: [
          {
            parameters: [{ text: 'John', type: 'text' }],
            type: 'body',
          },
        ],
        language: 'en_US',
        name: 'order_confirmation',
      },
    }),
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
      'X-Instance-ID': 'YOUR_INSTANCE_ID',
    },
    method: 'POST',
  })
  ```

  ```python Python theme={null}
  import requests

  requests.post(
      "https://api.zapsterapi.com/v1/wa/messages",
      headers={
          "Authorization": "Bearer YOUR_API_TOKEN",
          "X-Instance-ID": "YOUR_INSTANCE_ID",
      },
      json={
          "recipient": "5511999999999",
          "template": {
              "name": "order_confirmation",
              "language": "en_US",
              "components": [
                  {
                      "type": "body",
                      "parameters": [{"type": "text", "text": "John"}],
                  }
              ],
          },
      },
  )
  ```

  ```go Go theme={null}
  package main

  import (
  	"net/http"
  	"strings"
  )

  func main() {
  	body := strings.NewReader(`{
  		"recipient": "5511999999999",
  		"template": {
  			"name": "order_confirmation",
  			"language": "en_US",
  			"components": [
  				{
  					"type": "body",
  					"parameters": [{ "type": "text", "text": "John" }]
  				}
  			]
  		}
  	}`)

  	req, _ := http.NewRequest("POST", "https://api.zapsterapi.com/v1/wa/messages", body)
  	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
  	req.Header.Set("X-Instance-ID", "YOUR_INSTANCE_ID")
  	req.Header.Set("Content-Type", "application/json")

  	res, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	res.Body.Close()
  }
  ```
</CodeGroup>

### With header, positional variables, and buttons

This template has an image header, two positional variables in the body (`{{1}}` and `{{2}}`), and two buttons: a quick reply (with `payload`) and a dynamic URL (its text becomes the suffix appended to the template's base URL). The button `index` follows the order it appears in the approved template.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.zapsterapi.com/v1/wa/messages \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "X-Instance-ID: YOUR_INSTANCE_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "recipient": "5511999999999",
      "template": {
        "name": "order_shipped",
        "language": "en_US",
        "components": [
          {
            "type": "header",
            "parameters": [
              { "type": "image", "image": { "link": "https://example.com/banner.jpg" } }
            ]
          },
          {
            "type": "body",
            "parameters": [
              { "type": "text", "text": "John" },
              { "type": "text", "text": "#1234" }
            ]
          },
          {
            "type": "button",
            "sub_type": "quick_reply",
            "index": 0,
            "parameters": [
              { "type": "payload", "payload": "TRACK_ORDER" }
            ]
          },
          {
            "type": "button",
            "sub_type": "url",
            "index": 1,
            "parameters": [
              { "type": "text", "text": "order/1234" }
            ]
          }
        ]
      }
    }'
  ```

  ```javascript Node.js (axios) theme={null}
  const axios = require('axios')

  await axios.post(
    'https://api.zapsterapi.com/v1/wa/messages',
    {
      recipient: '5511999999999',
      template: {
        components: [
          {
            parameters: [
              { image: { link: 'https://example.com/banner.jpg' }, type: 'image' },
            ],
            type: 'header',
          },
          {
            parameters: [
              { text: 'John', type: 'text' },
              { text: '#1234', type: 'text' },
            ],
            type: 'body',
          },
          {
            index: 0,
            parameters: [{ payload: 'TRACK_ORDER', type: 'payload' }],
            sub_type: 'quick_reply',
            type: 'button',
          },
          {
            index: 1,
            parameters: [{ text: 'order/1234', type: 'text' }],
            sub_type: 'url',
            type: 'button',
          },
        ],
        language: 'en_US',
        name: 'order_shipped',
      },
    },
    {
      headers: {
        Authorization: 'Bearer YOUR_API_TOKEN',
        'X-Instance-ID': 'YOUR_INSTANCE_ID',
      },
    },
  )
  ```

  ```javascript Node.js (fetch) theme={null}
  await fetch('https://api.zapsterapi.com/v1/wa/messages', {
    body: JSON.stringify({
      recipient: '5511999999999',
      template: {
        components: [
          {
            parameters: [
              { image: { link: 'https://example.com/banner.jpg' }, type: 'image' },
            ],
            type: 'header',
          },
          {
            parameters: [
              { text: 'John', type: 'text' },
              { text: '#1234', type: 'text' },
            ],
            type: 'body',
          },
          {
            index: 0,
            parameters: [{ payload: 'TRACK_ORDER', type: 'payload' }],
            sub_type: 'quick_reply',
            type: 'button',
          },
          {
            index: 1,
            parameters: [{ text: 'order/1234', type: 'text' }],
            sub_type: 'url',
            type: 'button',
          },
        ],
        language: 'en_US',
        name: 'order_shipped',
      },
    }),
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
      'X-Instance-ID': 'YOUR_INSTANCE_ID',
    },
    method: 'POST',
  })
  ```

  ```python Python theme={null}
  import requests

  requests.post(
      "https://api.zapsterapi.com/v1/wa/messages",
      headers={
          "Authorization": "Bearer YOUR_API_TOKEN",
          "X-Instance-ID": "YOUR_INSTANCE_ID",
      },
      json={
          "recipient": "5511999999999",
          "template": {
              "name": "order_shipped",
              "language": "en_US",
              "components": [
                  {
                      "type": "header",
                      "parameters": [
                          {"type": "image", "image": {"link": "https://example.com/banner.jpg"}}
                      ],
                  },
                  {
                      "type": "body",
                      "parameters": [
                          {"type": "text", "text": "John"},
                          {"type": "text", "text": "#1234"},
                      ],
                  },
                  {
                      "type": "button",
                      "sub_type": "quick_reply",
                      "index": 0,
                      "parameters": [{"type": "payload", "payload": "TRACK_ORDER"}],
                  },
                  {
                      "type": "button",
                      "sub_type": "url",
                      "index": 1,
                      "parameters": [{"type": "text", "text": "order/1234"}],
                  },
              ],
          },
      },
  )
  ```

  ```go Go theme={null}
  package main

  import (
  	"net/http"
  	"strings"
  )

  func main() {
  	body := strings.NewReader(`{
  		"recipient": "5511999999999",
  		"template": {
  			"name": "order_shipped",
  			"language": "en_US",
  			"components": [
  				{
  					"type": "header",
  					"parameters": [
  						{ "type": "image", "image": { "link": "https://example.com/banner.jpg" } }
  					]
  				},
  				{
  					"type": "body",
  					"parameters": [
  						{ "type": "text", "text": "John" },
  						{ "type": "text", "text": "#1234" }
  					]
  				},
  				{
  					"type": "button",
  					"sub_type": "quick_reply",
  					"index": 0,
  					"parameters": [{ "type": "payload", "payload": "TRACK_ORDER" }]
  				},
  				{
  					"type": "button",
  					"sub_type": "url",
  					"index": 1,
  					"parameters": [{ "type": "text", "text": "order/1234" }]
  				}
  			]
  		}
  	}`)

  	req, _ := http.NewRequest("POST", "https://api.zapsterapi.com/v1/wa/messages", body)
  	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
  	req.Header.Set("X-Instance-ID", "YOUR_INSTANCE_ID")
  	req.Header.Set("Content-Type", "application/json")

  	res, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	res.Body.Close()
  }
  ```
</CodeGroup>

### Named variables

When the template was approved with named variables, each parameter carries a `parameter_name` in addition to `type` and `text`. The rest of the request (the `recipient`, the headers, the `name`, and the `language`) is the same as the examples above. Only the `components` content changes:

```json components (named variables) theme={null}
[
  {
    "type": "header",
    "parameters": [
      { "type": "text", "parameter_name": "company_name", "text": "Zapster" }
    ]
  },
  {
    "type": "body",
    "parameters": [
      { "type": "text", "parameter_name": "customer_name", "text": "John" },
      { "type": "text", "parameter_name": "order_id", "text": "#1234" }
    ]
  }
]
```

<Note>
  Do not mix positional and named variables in the same template. Use `parameter_name` only when the template was approved with named variables; otherwise, use the positional form (just `type` and `text`, in the order they appear).
</Note>

## Message billing

The official channel (WABA) is paid, and the party that charges for messages is Meta, not Zapster. The free messages in this guide are free until September 30, 2026 and start being charged from October 1, 2026. Templates keep their own charge.

To understand what changes in 2026, the dates, and how the price is set, see [WhatsApp official (WABA) message pricing](/en/v1/concepts/message-pricing).

## Common errors

When the payload uses a feature the Cloud API does not support, Zapster rejects the request right away with a `400` error explaining why. Nothing is silently dropped or adapted.

| Code                              | When it happens                                                                    | How to fix                                                                                                                                                   |
| --------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `waba_feature_not_supported`      | A `call` or `copyable` button in a session message, or audio together with buttons | For calls, use a template with a `PHONE_NUMBER` button. To copy a code, use a Marketing or Authentication template. For audio, send it in a separate message |
| `waba_invalid_button_combination` | Mixing a `url` button with `reply`, or more than 1 `url` button                    | Send 1 `url` button alone, or up to 3 `reply` buttons                                                                                                        |
| `waba_conversation_window_closed` | A free message outside the 24-hour window                                          | Send an approved template to reopen the conversation                                                                                                         |
| `waba_group_not_supported`        | A group `recipient` on a WABA instance                                             | Groups only work on unofficial instances                                                                                                                     |
| `waba_template_required`          | A `template` field sent to an unofficial instance                                  | Templates only exist on WABA instances                                                                                                                       |

## Next steps

* [Messages with buttons](/en/v1/guides/messages-with-buttons) for the full support matrix by connection type
* [Send messages to a BSUID](/en/v1/guides/send-to-bsuid) to reply to users with a hidden phone number (username / BSUID)
* [WABA vs unofficial](/en/v1/guides/waba-vs-unofficial) to choose the instance type
* [Connecting a WABA instance](/en/v1/guides/connect-waba-instance)
