Skip to content
WAPI · API reference

WhatsApp API docs: REST endpoints and webhooks.

Drive Flaxxa WAPI from your own software: a REST API with workspace keys, signed webhooks and idempotency keys.

No credit card
Free plan
Cancel anytime
flaxxa.com / product / wapi / docs
cURLREST · JSON
# Flaxxa WAPI · first request
curl https://flaxxa.com/api/wapi/v1 \
  -H "Authorization: Bearer $FLAXXA_KEY" \
  -H "Accept: application/json"

# webhooks: X-Flaxxa-Signature (HMAC-SHA256)
# retries: Idempotency-Key, 24 h
200 OKapplication/json
+ webhooks · HMAC-signed
REST
Requests per key by default
120/min
Idempotency window
24h
Quick start

How do I make my first API call?

Replace $FLAXXA_KEY with a key from your workspace. The first call returns a real id you can use for the next ones.

cURL
curl -X POST https://flaxxa.com/api/wapi/v1/messages/template \
  -H "Authorization: Bearer flx_live_YOUR_KEY" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{
    "to": "919876543210",
    "template": "order_update",
    "language": "en",
    "body_params": ["Asha", "ORD-18429"],
    "button_params": { "0": "ORD-18429" }
  }'
PHP
$res = Http::withToken(env('FLAXXA_WAPI_KEY'))->acceptJson()
    ->post('https://flaxxa.com/api/wapi/v1/messages/template', [
        'to'          => $customer->phone,          // E.164 or plain international
        'template'    => 'order_update',
        'language'    => 'en',
        'body_params' => [$customer->name, $order->number],
    ])->throw()->json();

// $res['message_id'], $res['message_wamid'], $res['status'] === 'success'
Node
const res = await fetch("https://flaxxa.com/api/wapi/v1/messages/text", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.FLAXXA_WAPI_KEY}`, Accept: "application/json", "Content-Type": "application/json" },
  body: JSON.stringify({ to: "919876543210", text: "Your order ORD-18429 has shipped 🚚" }),
});
const { data } = await res.json(); // data.id, data.wa_message_id, data.status
// text / media / buttons / list / flow need an open 24-hour window — otherwise send a template.
Python
import requests

r = requests.post("https://flaxxa.com/api/wapi/v1/messages/template",
    headers={"Authorization": f"Bearer {KEY}", "Accept": "application/json"},
    json={"to": "919876543210", "template": "order_update", "language": "en",
          "body_params": ["Asha", "ORD-18429"]})
r.raise_for_status()
print(r.json()["message_wamid"])
Request
POST /api/wapi/v1/messages/template HTTP/1.1
Host: flaxxa.com
Authorization: Bearer flx_live_·············
Content-Type: application/json

{
  "to":           "919876543210",
  "template":     "order_update",
  "language":     "en",
  "body_params":  ["Asha", "ORD-18429"],
  "button_params": { "0": "ORD-18429" }
}
Response · 201
HTTP/1.1 201 Created
Content-Type: application/json

{
  "status":        "success",
  "message_id":    9124,
  "message_wamid": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS…",
  "data": {
    "id": 9124, "wa_message_id": "wamid.HBgM…", "status": "sent",
    "conversation_id": 412, "wapi_number_id": 3, "to": "+919876543210",
    "type": "template", "sent_at": "2026-10-27T10:00:00+00:00"
  }
}
Authentication

How do I authenticate?

Every call requires Authorization: Bearer <key>. Rotate from workspace settings — old keys are revoked instantly. Server-side only; never ship a live key to the browser.

Base URLhttps://flaxxa.com/api/wapi/v1
Send messages

Send messages API

Every send takes `to` (E.164 or plain international, e.g. 919876543210) and an optional `number_id` (which of your connected numbers sends; a number-locked key always uses its own number and rejects a different one). Templates can be sent any time; text, media, buttons, list and flow messages need the customer's 24-hour window to be open — otherwise `422 window_closed`.

POST/messages/template

Send a template

{ to, template, language?, body_params (array for positional, object for named), header_media_url | header_media_id, button_params { "<button index>": value }, location, otp_code, components (Meta's exact array — sent verbatim), number_id }.

POST/messages/template/bulk

Template to many

{ template, language?, number_id?, recipients: [{ to, body_params?, … }] } — up to 100 per call; returns { sent, failed, results[] } with a per-recipient error code.

POST/messages/text

Send text

{ to, text (≤4096), number_id? } — inside the 24-hour window.

POST/messages/media

Send media

{ to, type: image|video|document|audio|sticker, url | media_id, caption?, filename?, number_id? }. Aliases: /messages/image, /video, /document, /audio, /sticker take the same body minus `type`.

POST/messages/buttons

Reply buttons

{ to, body (≤1024), buttons: [{ id, title ≤20 }] (1–3), header?, footer?, number_id? } — the tap comes back as wapi.message.received with type interactive.

POST/messages/list

List message

{ to, body, button (≤20), sections: [{ title?, rows: [{ id, title ≤24, description? }] }] (≤10 sections), header?, footer?, number_id? }.

POST/messages/flow

WhatsApp Flow (in-chat form)

{ to, flow_id (Meta id from GET /flows), flow_token (yours, ≤512 — returned on the submission), body, cta?, flow_action: navigate|data_exchange, screen?, flow_action_payload?, header?, footer?, number_id? }.

GET/messages/status

Delivery status

?message_id= or ?wa_message_id= → sent / delivered / read / failed with timestamps and the failure reason.

GET/messages

List messages

Recent messages across your numbers, paginated; filter by direction, number_id, phone.

POST/media

Upload media

multipart `file` (≤100 MB; Meta's per-type limits apply) + number_id? → { media_id } to reuse in sends instead of a URL.

Templates

Templates API

Templates are Meta-approved message layouts. Create and submit them here, or in the app; a template must be APPROVED before it can be sent.

GET/templates

List templates

Every template with its full, lossless structure: header (format, params), body (text, param_count, param_format POSITIONAL|NAMED, param_names), footer_text, buttons[] (index, type, url, example…), carousel, limited_time_offer, raw Meta components, and status.

POST/templates

Create + submit

{ name (lowercase_snake), language, category: MARKETING|UTILITY|AUTHENTICATION, components (Meta format), number_id? } — submitted to Meta for review.

GET/templates/status

Review status

?name= → APPROVED / PENDING / REJECTED with Meta's reason.

Contacts

Contacts API

The people you message. Sending to a new number creates the contact automatically; these calls let you keep names and opt-in state in sync.

GET/contacts

List contacts

Paginated; search by phone or name.

GET/contacts/{phone}

Get by phone

Contact, opt-in status and last conversation for a phone number.

POST/contacts

Create contact

{ phone, name?, email? } — scope wapi.broadcast.create.

PUT/contacts/{phone}

Update contact

{ name?, email? }.

Bots & Flows

Bots & Flows API

Start a reply-bot conversation for a number, and discover the WhatsApp Flows published on each of your numbers.

GET/bots

List bots

Active chatbots with their ids and trigger words.

POST/bots/trigger

Start a bot

{ to, bot_id, variables?, client_ref?, number_id? } — opens the bot for that customer; `client_ref` is echoed on every event the session produces.

GET/flows

Published flows

?number_id= → flows with status published and a Meta id — exactly the ones POST /messages/flow can send (Flow ids differ per WABA).

Webhooks

Webhooks API

Register a URL per workspace (or per number) and receive inbound messages, delivery receipts, reactions and call events. Deliveries carry X-Wapi-Event, X-Wapi-Delivery (unique — de-duplicate on it) and X-Wapi-Signature: sha256=HMAC-SHA256(raw body, secret). Events that originate from Meta are delivered as Meta's own envelope with a `flaxxa` block added to each entry; Flaxxa-originated events are the bare { event, timestamp, data }. Read robustly: `ev = body.flaxxa ?? body.entry?.[0]?.flaxxa ?? body`.

POST/webhooks

Register

{ url, events?: [names] (omit = all), secret?, name?, number_id? } — scope wapi.message.send.

GET/webhooks

List

Registered endpoints with last delivery status.

DELETE/webhooks/{id}

Remove

Stops delivery.

Account & numbers

Account & numbers API

A key belongs to one workspace and is normally LOCKED to one WhatsApp number (set when the key is created on WAPI → Developers). A locked key ignores/rejects a conflicting `number_id`; an unlocked key defaults to the workspace's default number.

GET/me

Who am I

The key's workspace, scopes, bound number and connected numbers — the first call to make when wiring an integration.

GET/openapi.json

OpenAPI 3.1 spec

The machine-readable contract (no key needed). Human-readable reference: https://flaxxa.com/wapi/api-docs.

Webhook events

Which webhook events can I subscribe to?

Subscribe via POST /webhooks. Verify X-Flaxxa-Signature on every payload.

  • wapi.message.received

    A customer sent a message (text, media, button/list reply, or a Flow submission — data.type = "flow" with data.flow.token and data.flow.response).

  • wapi.message.sent

    A message you sent through the API was accepted by WhatsApp.

  • wapi.message.delivered

    WhatsApp delivered your message to the customer's device.

  • wapi.message.read

    The customer read your message.

  • wapi.message.failed

    WhatsApp could not deliver the message; data carries Meta's error code and reason.

  • wapi.message.reaction

    The customer reacted to a message with an emoji.

  • wapi.call.connect

    A WhatsApp call connected on one of your numbers.

  • wapi.call.terminate

    A WhatsApp call ended.

Errors

What errors can the API return?

All errors return a JSON envelope: { error: { code, message, details } }. Retry idempotent calls on 5xx with exponential backoff.

CodeNameWhen it fires
401invalid_api_keyNo Bearer key, or the key is invalid, revoked, expired, or its workspace/creator is no longer active.
403insufficient_scopeThe key lacks the scope — sends need wapi.message.send; reads need wapi.inbox.view; templates need wapi.template.view / .create; contacts writes need wapi.broadcast.create.
404number_not_foundNo connected WhatsApp number with that number_id in this workspace.
422no_numberThe workspace has no connected WhatsApp number yet.
422invalid_phone`to` is not a valid phone number.
422window_closedThe 24-hour customer window is closed — send an approved template instead.
422template_not_approvedThe template exists but is not APPROVED on Meta yet.
422contact_blockedThe contact is blocked in your inbox.
429rate_limitedOver the key's per-minute limit (120/min by default); Retry-After is set.
502meta_<code>WhatsApp rejected the message; the message carries Meta's error text.
Conventions

Which rules apply to every request?

Every Flaxxa app's API follows the same authentication, rate limits, idempotency and webhook signing.

Bearer auth

Every API call uses a Bearer token. Keys belong to a workspace and are scoped per app. Create and revoke them from workspace API settings.

Rate limits

Each key can make 120 requests a minute unless the app sets its own limit. Going over returns 429 with a Retry-After header.

Idempotency

Send an Idempotency-Key header on a POST and a retry with the same key within 24 hours returns the first response instead of running again.

Signed webhooks

Every webhook payload carries an X-Flaxxa-Signature HMAC-SHA256 header made with the secret you get when you subscribe. Verify it before processing.

API FAQ

Flaxxa WAPI API: common questions

Frequently asked questions

Yes. A REST API at https://flaxxa.com/api/wapi/v1, called with a workspace API key scoped to Flaxxa WAPI.

Get started in minutes

Ship faster with Flaxxa WAPI.

Create a workspace, make an API key in its settings, and send your first request.

Free plan · No credit card · Cancel anytime