GhalaGhalaHelp Center
Back to ghala.ioghala.ioSign in
  • Getting Started
    • Ghala Documentation
    • Send Your First WhatsApp Message via API
    • Receiving Events with Webhooks
  • Commerce
    • Start Selling on WhatsApp
    • Set Up the AI Sales Agent
    • Get Paid with Snippe
  • Ai Automation
    • Configuring AI Auto-Reply for WhatsApp
    • Setting Up Human Handover Protocol
  • Campaigns Messaging
    • WhatsApp Message Templates: Complete Guide
    • Bulk WhatsApp Messaging: Complete Campaign Guide
  • Contacts Crm
    • WhatsApp Contact Management Guide
  • Best Practices
    • WhatsApp Customer Support Best Practices
    • Message Template Best Practices
  • Api Reference
    • Ghala Developer API Reference
    • Ghala API vs Meta Cloud API
    • Supported Capabilities
    • Multiple Numbers and Multi-Tenant Platforms
    • Errors and Retries
    • Limits and Quotas
    • Authentication and Access Tokens
    • Connecting and Onboarding a Number
    • Templates and Media
    • Developer Tooling
    • Versioning and Changelog

Products

  • Ghala
  • Sarufi
  • Snippe
  • Sema

Explore

  • Use Cases
  • Pricing
  • Ghala Academy
  • Blog

Developers

  • Docs
  • API Reference
  • API Quickstart
  • Webhooks Guide

Contact

  • SkyCity Mall, 9th Floor, Dar es Salaam, Tanzania
  • info@ghala.io
  • +255 699 920 009
© 2026 Neurotech Company LimitedTerms of ServicePrivacy PolicySitemap
  1. Help Center
  2. Getting Started
  3. Send Your First WhatsApp Message via API

Send Your First WhatsApp Message via API

v2

From access token to delivered message in five minutes, with cURL, Python, and JavaScript examples.

Before you start

You need three things:

  1. A connected WhatsApp number, showing as live. Connect one from the account switcher in your dashboard; the wizard walks you through Meta authorization and verification. See Connecting and Onboarding a Number if it gets stuck.
  2. The number's access token. Open Developer → Credentials, reveal the token, and copy it. This is the same secret Ghala uses to send on your behalf, so keep it in an environment variable, server-side, never in browser code or git.
  3. A plan that includes API access. Without it every call returns 402 plan_feature_locked; upgrade under Settings → Billing.

All requests go to:

https://v2.ghala.io/api/v2

with your token in the Authorization header. That prefix is the API version, and v2 is the current one.

Reconnecting the number issues a new token and invalidates the old one. If a working integration suddenly returns 401, that is almost always why.

Check your token before you send

The cheapest way to prove authentication works is a call that messages nobody:

curl "https://v2.ghala.io/api/v2/templates?limit=1" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

A 200 means your token is good and the plan is right. A 401 means the token is wrong or stale; a 402 means the plan does not include API access.

Open the 24-hour window first

WhatsApp only allows free-form messages within 24 hours of the customer's last message to you. Outside that window, only an approved template can be sent.

So before your first test: message the number from a real WhatsApp handset. That opens the window and lets the text example below succeed. Skip this and you get:

{ "code": "outside_messaging_window", "message": "..." }

with status 409. That is the API telling you the rule up front rather than letting Meta reject the send later.

Send a text message

Phone numbers use full international format without + (e.g. 255712345678).

curl -X POST https://v2.ghala.io/api/v2/messages \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "255712345678",
    "type": "text",
    "text": "Habari! Hii ni ujumbe wa majaribio."
  }'
import os, requests

res = requests.post(
    "https://v2.ghala.io/api/v2/messages",
    headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
    json={
        "to": "255712345678",
        "type": "text",
        "text": "Habari! Hii ni ujumbe wa majaribio.",
    },
)
print(res.json())
const res = await fetch("https://v2.ghala.io/api/v2/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "255712345678",
    type: "text",
    text: "Habari! Hii ni ujumbe wa majaribio.",
  }),
});
console.log(await res.json());

A successful send returns the recorded message:

{
  "id": "01JZ8Q4R2K7N3M5P9V1X6T0B2C",
  "direction": "OUTBOUND",
  "message_type": "text",
  "content": "Habari! Hii ni ujumbe wa majaribio.",
  "status": "sent",
  "wa_message_id": "wamid.HBgM...",
  "created_at": "2026-08-05T09:14:22Z"
}

Send it to your own WhatsApp number first; it should land on your phone within seconds. There is no sandbox and no test mode, so every send is a real message to a real handset.

Your send pauses the AI agent

Sending through the API stands the AI agent down for that customer, exactly as replying from the dashboard inbox does. Otherwise the assistant would answer alongside you and the customer would hear two voices.

The agent resumes on its own after the number's takeover window, or immediately if the customer texts BOT. Worth knowing before you wire the API into something that sends often: every send is a handover.

Send a template message

Templates are the only thing you can send outside the 24-hour window, so they are how you start a conversation, send a reminder, or follow up on an order.

Ask the API which ones you may send:

curl "https://v2.ghala.io/api/v2/templates?sendable=true" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Then send one:

curl -X POST https://v2.ghala.io/api/v2/messages \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "255712345678",
    "type": "template",
    "template_name": "hello_world",
    "template_language": "en_US"
  }'
import os, requests

res = requests.post(
    "https://v2.ghala.io/api/v2/messages",
    headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
    json={
        "to": "255712345678",
        "type": "template",
        "template_name": "hello_world",
        "template_language": "en_US",
    },
)
print(res.json())
const res = await fetch("https://v2.ghala.io/api/v2/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "255712345678",
    type: "template",
    template_name: "hello_world",
    template_language: "en_US",
  }),
});
console.log(await res.json());

Create and submit templates under Templates in the dashboard; Meta approval usually takes minutes to a few hours.

For a template with variables, pass template_components in Meta's own component format — Templates and Media has worked examples. A 502 back means WhatsApp refused it, usually because the template is not approved or the name and language do not match.

Send more than text

The same endpoint sends media and interactive menus. type picks the shape:

{
  "to": "255712345678",
  "type": "interactive",
  "interactive": {
    "type": "buttons",
    "body": "Oda yako #1042 iko tayari. Ungependa vipi kuipata?",
    "buttons": [
      { "id": "delivery", "title": "Niletewe" },
      { "id": "pickup", "title": "Nitakuja kuchukua" }
    ]
  }
}

The id you set comes back to you when the customer taps, so make it something you can act on. Full list of types in the API Reference.

Retry safely from the start

Add an Idempotency-Key header to any send your code might retry, and every integration retries eventually:

curl -X POST https://v2.ghala.io/api/v2/messages \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: order-1042-confirmation" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "255712345678",
    "type": "text",
    "text": "Asante! Oda yako #1042 imethibitishwa."
  }'

Run it twice and the customer still receives one message: the second call replays the first response with Idempotency-Replayed: true in the headers, and nothing reaches WhatsApp.

Derive the key from the thing you are messaging about, not from a random value, so a retry after a crash reuses the same key. That is also how you recover when a request times out and you do not know whether it landed — see Errors and Retries.

What happens after "sent"

sent only means WhatsApp accepted the message. Delivery and read receipts arrive asynchronously, and so do your customers' replies. That is the second half of any integration:

→ Receive events with webhooks to get replies and status updates in real time.

For every endpoint, parameter, and error code, see the API Reference.

PreviousGhala DocumentationNextReceiving Events with Webhooks

On this page

  • Before you start
  • Check your token before you send
  • Open the 24-hour window first
  • Send a text message
  • Your send pauses the AI agent
  • Send a template message
  • Send more than text
  • Retry safely from the start
  • What happens after "sent"