> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.teekrr.com/llms.txt.
> For full documentation content, see https://docs.teekrr.com/llms-full.txt.

# Send WhatsApp broadcast

POST https://api.teekrr.com/whatsapp
Content-Type: application/json

Creates a broadcast, inserts one row per recipient into `whatsapp_messages`,
atomically reserves credit (prepaid), and enqueues to AWS SQS for delivery
via the META Cloud API. Final status reaches your registered webhook URL.

**Required scope:** `send_whatsapp`
**Rate limit:** 10 requests / 15 min / client
**Status lifecycle:** `pending → enqueued → sent → [delivered | read | failed]`

## Template requirement

WhatsApp messages are template-only — `templateName` is required and must
reference an **approved** template owned by your client. Templates are
managed in the Teekrr dashboard and pass through META's approval flow.

If your client does not have its own WhatsApp Business Account (WABA),
Teekrr falls back to the platform-default shared WABA automatically.

## Variables

```json
{
  "header":         ["string"],
  "body":           ["string"],
  "button":         ["string"],
  "headerImage":    "https://...",
  "headerVideo":    "https://...",
  "headerDocument": "https://..."
}
```

For media headers, upload the asset first via `POST /whatsapp/upload-header-image`
and pass the returned `url`.


Reference: https://docs.teekrr.com/api-reference/whats-app/send-whatsapp

## OpenAPI Specification

```yaml
openapi: 3.1.0
info:
  title: Teekrr Public API
  version: 1.0.0
paths:
  /whatsapp:
    post:
      operationId: send-whatsapp
      summary: Send WhatsApp broadcast
      description: >
        Creates a broadcast, inserts one row per recipient into
        `whatsapp_messages`,

        atomically reserves credit (prepaid), and enqueues to AWS SQS for
        delivery

        via the META Cloud API. Final status reaches your registered webhook
        URL.


        **Required scope:** `send_whatsapp`

        **Rate limit:** 10 requests / 15 min / client

        **Status lifecycle:** `pending → enqueued → sent → [delivered | read |
        failed]`


        ## Template requirement


        WhatsApp messages are template-only — `templateName` is required and
        must

        reference an **approved** template owned by your client. Templates are

        managed in the Teekrr dashboard and pass through META's approval flow.


        If your client does not have its own WhatsApp Business Account (WABA),

        Teekrr falls back to the platform-default shared WABA automatically.


        ## Variables


        ```json

        {
          "header":         ["string"],
          "body":           ["string"],
          "button":         ["string"],
          "headerImage":    "https://...",
          "headerVideo":    "https://...",
          "headerDocument": "https://..."
        }

        ```


        For media headers, upload the asset first via `POST
        /whatsapp/upload-header-image`

        and pass the returned `url`.
      tags:
        - subpackage_whatsApp
      parameters:
        - name: Authorization
          in: header
          description: >
            Bearer API keys are issued from the in-app `/api-management` page.
            Each key has

            a permission scope (`send_sms`, `send_whatsapp`, `send_email`) and
            an optional

            IP whitelist.
          required: true
          schema:
            type: string
      responses:
        '202':
          description: Broadcast accepted (queued or scheduled)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WhatsappSuccess'
        '400':
          description: Body validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Missing, invalid, revoked, or expired API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: >-
            Prepaid balance below required cost, or postpaid spending cap
            exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller IP not whitelisted, missing scope, or channel inactive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Referenced template, keyword, or WhatsApp configuration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Template not approved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Per-channel per-client rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Unexpected server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendWhatsappRequest'
servers:
  - url: https://api.teekrr.com
  - url: https://api.staging.teekrr.com
components:
  schemas:
    BroadcastType:
      type: string
      enum:
        - quick broadcast
        - schedule broadcast
      description: |
        - `quick broadcast` — send immediately
        - `schedule broadcast` — defer; requires `scheduledAt`
      title: BroadcastType
    PhoneNumberE164:
      type: string
      description: E.164-format phone number (digits only or with leading `+`).
      title: PhoneNumberE164
    WhatsappVariables:
      type: object
      properties:
        header:
          type: array
          items:
            type: string
          description: Template header positional substitutions.
        body:
          type: array
          items:
            type: string
          description: Template body positional substitutions.
        button:
          type: array
          items:
            type: string
          description: Dynamic button text.
        headerImage:
          type: string
          format: uri
          description: >-
            URL of an uploaded header image (use `POST
            /whatsapp/upload-header-image`).
        headerVideo:
          type: string
          format: uri
        headerDocument:
          type: string
          format: uri
      title: WhatsappVariables
    WhatsappRecipientVariables:
      type: object
      properties:
        msisdn:
          $ref: '#/components/schemas/PhoneNumberE164'
        variables:
          $ref: '#/components/schemas/WhatsappVariables'
      required:
        - msisdn
        - variables
      title: WhatsappRecipientVariables
    SendWhatsappRequest:
      type: object
      properties:
        templateName:
          type: string
        campaignName:
          type: string
        type:
          $ref: '#/components/schemas/BroadcastType'
        scheduledAt:
          type: string
          format: date-time
        recipients:
          type: array
          items:
            $ref: '#/components/schemas/PhoneNumberE164'
        variables:
          $ref: '#/components/schemas/WhatsappVariables'
        recipientVariables:
          type: array
          items:
            $ref: '#/components/schemas/WhatsappRecipientVariables'
      required:
        - templateName
        - campaignName
        - type
      title: SendWhatsappRequest
    WhatsappSuccessData:
      type: object
      properties:
        broadcastUuid:
          type: string
          format: uuid
        queued:
          type: integer
        sqsMessageIds:
          type: array
          items:
            type: string
        scheduledAt:
          type: string
          format: date-time
      required:
        - broadcastUuid
        - queued
      title: WhatsappSuccessData
    WhatsappSuccess:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/WhatsappSuccessData'
      title: WhatsappSuccess
    ValidationErrorResponseErrorsItems:
      type: object
      properties:
        field:
          type: string
        message:
          type: string
      required:
        - field
        - message
      title: ValidationErrorResponseErrorsItems
    ValidationErrorResponse:
      type: object
      properties:
        message:
          type: string
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ValidationErrorResponseErrorsItems'
      required:
        - message
        - errors
      title: ValidationErrorResponse
    ErrorResponse:
      type: object
      properties:
        message:
          type: string
      required:
        - message
      title: ErrorResponse
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >
        Bearer API keys are issued from the in-app `/api-management` page. Each
        key has

        a permission scope (`send_sms`, `send_whatsapp`, `send_email`) and an
        optional

        IP whitelist.

```

## SDK Code Examples

```python queued
import requests

url = "https://api.teekrr.com/whatsapp"

headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript queued
const url = 'https://api.teekrr.com/whatsapp';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: undefined
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go queued
package main

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

func main() {

	url := "https://api.teekrr.com/whatsapp"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby queued
require 'uri'
require 'net/http'

url = URI("https://api.teekrr.com/whatsapp")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'

response = http.request(request)
puts response.read_body
```

```java queued
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.teekrr.com/whatsapp")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .asString();
```

```php queued
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.teekrr.com/whatsapp', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp queued
using RestSharp;

var client = new RestClient("https://api.teekrr.com/whatsapp");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
IRestResponse response = client.Execute(request);
```

```swift queued
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.teekrr.com/whatsapp")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

```python Standard mode
import requests

url = "https://api.teekrr.com/whatsapp"

payload = {
    "templateName": "welcome_promo_v1",
    "campaignName": "May 2026 Welcome Promo",
    "type": "quick broadcast",
    "recipients": ["60123456789", "60198765432"],
    "variables": { "body": ["Ali", "RM50 OFF"] }
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Standard mode
const url = 'https://api.teekrr.com/whatsapp';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"templateName":"welcome_promo_v1","campaignName":"May 2026 Welcome Promo","type":"quick broadcast","recipients":["60123456789","60198765432"],"variables":{"body":["Ali","RM50 OFF"]}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Standard mode
package main

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

func main() {

	url := "https://api.teekrr.com/whatsapp"

	payload := strings.NewReader("{\n  \"templateName\": \"welcome_promo_v1\",\n  \"campaignName\": \"May 2026 Welcome Promo\",\n  \"type\": \"quick broadcast\",\n  \"recipients\": [\n    \"60123456789\",\n    \"60198765432\"\n  ],\n  \"variables\": {\n    \"body\": [\n      \"Ali\",\n      \"RM50 OFF\"\n    ]\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Standard mode
require 'uri'
require 'net/http'

url = URI("https://api.teekrr.com/whatsapp")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"templateName\": \"welcome_promo_v1\",\n  \"campaignName\": \"May 2026 Welcome Promo\",\n  \"type\": \"quick broadcast\",\n  \"recipients\": [\n    \"60123456789\",\n    \"60198765432\"\n  ],\n  \"variables\": {\n    \"body\": [\n      \"Ali\",\n      \"RM50 OFF\"\n    ]\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java Standard mode
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.teekrr.com/whatsapp")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"templateName\": \"welcome_promo_v1\",\n  \"campaignName\": \"May 2026 Welcome Promo\",\n  \"type\": \"quick broadcast\",\n  \"recipients\": [\n    \"60123456789\",\n    \"60198765432\"\n  ],\n  \"variables\": {\n    \"body\": [\n      \"Ali\",\n      \"RM50 OFF\"\n    ]\n  }\n}")
  .asString();
```

```php Standard mode
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.teekrr.com/whatsapp', [
  'body' => '{
  "templateName": "welcome_promo_v1",
  "campaignName": "May 2026 Welcome Promo",
  "type": "quick broadcast",
  "recipients": [
    "60123456789",
    "60198765432"
  ],
  "variables": {
    "body": [
      "Ali",
      "RM50 OFF"
    ]
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Standard mode
using RestSharp;

var client = new RestClient("https://api.teekrr.com/whatsapp");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"templateName\": \"welcome_promo_v1\",\n  \"campaignName\": \"May 2026 Welcome Promo\",\n  \"type\": \"quick broadcast\",\n  \"recipients\": [\n    \"60123456789\",\n    \"60198765432\"\n  ],\n  \"variables\": {\n    \"body\": [\n      \"Ali\",\n      \"RM50 OFF\"\n    ]\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Standard mode
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "templateName": "welcome_promo_v1",
  "campaignName": "May 2026 Welcome Promo",
  "type": "quick broadcast",
  "recipients": ["60123456789", "60198765432"],
  "variables": ["body": ["Ali", "RM50 OFF"]]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.teekrr.com/whatsapp")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

```python Dynamic mode
import requests

url = "https://api.teekrr.com/whatsapp"

payload = {
    "templateName": "order_shipped_v2",
    "campaignName": "Shipping notifications 2026-05-06",
    "type": "quick broadcast",
    "recipientVariables": [
        {
            "msisdn": "60123456789",
            "variables": { "body": ["Ali", "TKR123ABC"] }
        },
        {
            "msisdn": "60198765432",
            "variables": { "body": ["Fatimah", "TKR456DEF"] }
        }
    ]
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Dynamic mode
const url = 'https://api.teekrr.com/whatsapp';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"templateName":"order_shipped_v2","campaignName":"Shipping notifications 2026-05-06","type":"quick broadcast","recipientVariables":[{"msisdn":"60123456789","variables":{"body":["Ali","TKR123ABC"]}},{"msisdn":"60198765432","variables":{"body":["Fatimah","TKR456DEF"]}}]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Dynamic mode
package main

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

func main() {

	url := "https://api.teekrr.com/whatsapp"

	payload := strings.NewReader("{\n  \"templateName\": \"order_shipped_v2\",\n  \"campaignName\": \"Shipping notifications 2026-05-06\",\n  \"type\": \"quick broadcast\",\n  \"recipientVariables\": [\n    {\n      \"msisdn\": \"60123456789\",\n      \"variables\": {\n        \"body\": [\n          \"Ali\",\n          \"TKR123ABC\"\n        ]\n      }\n    },\n    {\n      \"msisdn\": \"60198765432\",\n      \"variables\": {\n        \"body\": [\n          \"Fatimah\",\n          \"TKR456DEF\"\n        ]\n      }\n    }\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Dynamic mode
require 'uri'
require 'net/http'

url = URI("https://api.teekrr.com/whatsapp")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"templateName\": \"order_shipped_v2\",\n  \"campaignName\": \"Shipping notifications 2026-05-06\",\n  \"type\": \"quick broadcast\",\n  \"recipientVariables\": [\n    {\n      \"msisdn\": \"60123456789\",\n      \"variables\": {\n        \"body\": [\n          \"Ali\",\n          \"TKR123ABC\"\n        ]\n      }\n    },\n    {\n      \"msisdn\": \"60198765432\",\n      \"variables\": {\n        \"body\": [\n          \"Fatimah\",\n          \"TKR456DEF\"\n        ]\n      }\n    }\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java Dynamic mode
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.teekrr.com/whatsapp")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"templateName\": \"order_shipped_v2\",\n  \"campaignName\": \"Shipping notifications 2026-05-06\",\n  \"type\": \"quick broadcast\",\n  \"recipientVariables\": [\n    {\n      \"msisdn\": \"60123456789\",\n      \"variables\": {\n        \"body\": [\n          \"Ali\",\n          \"TKR123ABC\"\n        ]\n      }\n    },\n    {\n      \"msisdn\": \"60198765432\",\n      \"variables\": {\n        \"body\": [\n          \"Fatimah\",\n          \"TKR456DEF\"\n        ]\n      }\n    }\n  ]\n}")
  .asString();
```

```php Dynamic mode
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.teekrr.com/whatsapp', [
  'body' => '{
  "templateName": "order_shipped_v2",
  "campaignName": "Shipping notifications 2026-05-06",
  "type": "quick broadcast",
  "recipientVariables": [
    {
      "msisdn": "60123456789",
      "variables": {
        "body": [
          "Ali",
          "TKR123ABC"
        ]
      }
    },
    {
      "msisdn": "60198765432",
      "variables": {
        "body": [
          "Fatimah",
          "TKR456DEF"
        ]
      }
    }
  ]
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Dynamic mode
using RestSharp;

var client = new RestClient("https://api.teekrr.com/whatsapp");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"templateName\": \"order_shipped_v2\",\n  \"campaignName\": \"Shipping notifications 2026-05-06\",\n  \"type\": \"quick broadcast\",\n  \"recipientVariables\": [\n    {\n      \"msisdn\": \"60123456789\",\n      \"variables\": {\n        \"body\": [\n          \"Ali\",\n          \"TKR123ABC\"\n        ]\n      }\n    },\n    {\n      \"msisdn\": \"60198765432\",\n      \"variables\": {\n        \"body\": [\n          \"Fatimah\",\n          \"TKR456DEF\"\n        ]\n      }\n    }\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Dynamic mode
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "templateName": "order_shipped_v2",
  "campaignName": "Shipping notifications 2026-05-06",
  "type": "quick broadcast",
  "recipientVariables": [
    [
      "msisdn": "60123456789",
      "variables": ["body": ["Ali", "TKR123ABC"]]
    ],
    [
      "msisdn": "60198765432",
      "variables": ["body": ["Fatimah", "TKR456DEF"]]
    ]
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.teekrr.com/whatsapp")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```