12 min read

Connecting your software to WhatsApp

Issue an API key, map an approved WhatsApp template, point a webhook at your own server, and call the REST API — the complete guide to API Management.

Connecting your software to WhatsApp

API Management is how another system — your billing software, your online store, your own backend — sends WhatsApp messages through WizMessage. You issue it an API key here, tell it which approved WhatsApp template it may send, and point it at a webhook so replies come back. This guide walks the whole path, then documents the REST API your developers will call.

The dashboard

Where your API keys live

Every key you have issued appears here under API Keys. Each row shows the key's status, its public prefix (the only part of the key ever shown again after you create it), and how many calls it has made today. "Manage" opens the key; the toggle pauses it without deleting it.

Video walkthrough — 2 steps, 0:05. Pick a step to play just that part.
The API Keys dashboard listing every issued key with its status, public prefix and daily call count

A key that is not finished yet

A key with no template mapped to it carries a "Needs setup" badge. It can authenticate, but it cannot send a template message until you map one — so a half-configured integration is visible at a glance rather than failing later at send time.

An API key row showing the Needs setup badge because no template is mapped to it yet

Guided setup

Step 1 — What are you connecting?

"Connect your software" opens a four-step wizard. Start by naming the key after the system that will use it, and pick the preset that matches. The presets only suggest sensible field names — billing suggests invoice_number, amount, due_date, customer_name — they do not restrict what you can map.

Video walkthrough — 4 steps, 0:19. Pick a step to play just that part.
Step 1 of the wizard, naming the API key and choosing the preset that matches the connecting system

Step 2 — Choose the message

Pick one of your approved WhatsApp templates. Templates live at Meta, not here — this list is fetched live from your connected WhatsApp account, and only APPROVED templates can be sent. This step is optional: skip it if the key only ever sends plain text, and map a template later.

Step 2 of the wizard, choosing one of the approved WhatsApp templates fetched live from Meta

Step 3 — Where should replies go?

When someone replies to your message, or a delivery report comes back, we POST it to a URL you control. Leave this off if your system only ever sends. You can add it later from the key's Webhooks tab — nothing here is permanent.

Step 3 of the wizard, setting the webhook URL that replies and delivery reports are posted to

Step 4 — Go live

Pressing Next on the previous step is what actually creates the key — this screen is the confirmation. Copy all three values now: the API key, the API secret, and the webhook signing secret are shown exactly once and cannot be recovered afterwards. If you lose the secret you must rotate it, which means updating whatever system was using it.

Step 4 of the wizard, showing the API key, API secret and webhook signing secret exactly once

Managing a key

Overview

Opening a key lands on Overview: what it is allowed to do, how heavily it is being used, and the endpoints it can call. Usage counts are read from the request log, so they reflect real traffic.

Video walkthrough — 1 step, 0:05.
The Overview tab of an API key showing its permissions, usage and callable endpoints

A key that still needs setting up

A key with no template mapped opens on a Setup tab instead — the same wizard, inline. Note the tabs differ from the previous screenshot: until a key has at least one template, Templates and Webhooks are not offered, because there is nothing yet for them to act on.

Video walkthrough — 1 step, 0:05.
An unconfigured key opening on the Setup tab, with the Templates and Webhooks tabs not yet offered

Templates

Each card is one template this key may send. The body text is the message as approved at Meta, and the chips beneath it are the variable names your system will supply. "Try it" sends a test, "Get code" produces a ready-made request in eight languages.

Video walkthrough — 2 steps, 0:07. Pick a step to play just that part.
The Templates tab, one card per template with its approved body text and variable name chips

Reading the variables — the one thing worth slowing down for

Look at the payment_failed_alert card. Its message runs {{1}}, then {{3}}, then {{2}} — the placeholders are NOT in numeric order in the text. The chips below are listed in numeric order: customer_name fills {{1}}, renewal_date fills {{2}}, plan_name fills {{3}}. Match your values to the NUMBER, never to the order the placeholders happen to appear in the sentence. This trips people up most often on translated templates, where word order moves the placeholders around.

A template card whose placeholders appear out of numeric order in the message body

The three buttons on every template

"Try it" — send a test without writing any code

The paper-plane button opens this template pre-loaded with sample values, one per variable name. It is the fastest way to check a mapping is right before your developers touch it. Note the toggle: "Simulate Only" is the default and nothing leaves the building — the server echoes your payload straight back. Flip it to "Send Real Message" and a genuine WhatsApp message goes to the number in the box, and is charged for.

Video walkthrough — 7 steps, 0:21. Pick a step to play just that part.
The Try it dialog, pre-loaded with one sample value per variable and a Simulate Only toggle

What a simulated send gives you back

Pressing Send Request in Simulate Only mode returns the request as the platform received it, with the HTTP status and round-trip time. Read this as a shape check, not a rehearsal: it confirms your JSON parsed and the key was accepted, but it does NOT validate your variable names against the mapping and never contacts Meta. A payload that simulates cleanly can still be rejected for real. To prove the mapping itself, turn on Send Real Message and send to your own phone.

The response of a simulated send, echoing the request back with its HTTP status and round-trip time

"Get code" — the request, written for you

The angle-brackets button generates a working request for this exact template in eight languages: cURL, JavaScript, Node.js, Python, PHP, Ruby, Go and C#. Hand it to whoever is writing the integration. The variables appear as {{placeholders}} named after your mapping, and the credentials are left as YOUR_API_KEY / YOUR_API_SECRET — this dialog never prints your real key, so the snippet is safe to paste into a ticket.

The Get code dialog showing a ready-made request for this template with placeholder credentials

Switching language

Click any language along the top and the snippet is rewritten for it — the same call, the same headers, that language's HTTP client. Each one is generated the first time you click it, so a brief spinner on first switch is normal. Copy takes the snippet as shown: it is complete, imports and all, and if this key requires signatures the signing step is already in it.

The same request rewritten for another language after clicking it in the language row

"Edit" — change what your system sends

The pencil opens the mapping itself. The phone mock-up at the top is the real message with your current names substituted in, so you can see what the customer will read. Beneath it, each detected variable can be renamed, reordered, marked required, or given a type — a date, a currency amount, an image. Renaming a variable renames it in your API payload too, so a system already sending the old name will start failing: change both together.

The Edit mapping dialog with a phone preview above the renameable, reorderable variable list

Test API — the same tool, any endpoint

"Test API" at the top of the tab is "Try it" without the template lock: pick any of the three send endpoints, switch templates from the second dropdown, and edit the body freely. Use it to check the raw /messages/text or /messages/template calls, which have no card of their own because they are not tied to a mapping. Note this template's variables are still called body_1, body_2, body_3 — that is what an unmapped template looks like, and it is what "Edit" exists to fix.

The Test API dialog with an endpoint dropdown, a template dropdown and a freely editable body

Replies

Webhooks — getting replies back

Sending is only half an integration. Point a webhook at your own HTTPS endpoint and WizMessage will POST inbound messages and delivery updates to it. The signing secret is shown once when generated; verify it on every delivery so you know the call really came from us.

The Webhooks tab, where an HTTPS endpoint receives inbound messages and delivery updates

REST API reference

Everything above configures the key. This is what your developers actually call. Base URL is /api/v1/external. Every endpoint requires the auth headers below and is subject to the key's rate limits.

Authenticating

Send your API key on every request. If the key has "Require signature" enabled, you must also sign the request — and a key with it enabled will reject any unsigned call outright.

HeaderValue
X-API-KeyYour API key — the full pk_live_… value, not just the prefix shown in the dashboard.
X-TimestampUnix timestamp in seconds. Rejected if more than 5 minutes from server time.
X-API-SignatureHMAC-SHA256 of timestamp + "." + rawBody, keyed on SHA256(api_secret), lowercase hex.

Note: Signatures are only enforced when the key has require_signature enabled. If it does not, the API key alone is sufficient to send messages — which is exactly why the key is a secret, not an identifier. Treat it like a password: it is shown once, and anyone holding it can message your customers.

const crypto = require('node:crypto')

const body = JSON.stringify({ to: '+919876543210', message: 'Hello' })
const timestamp = Math.floor(Date.now() / 1000).toString()

const secretHash = crypto.createHash('sha256').update(API_SECRET).digest('hex')
const signature = crypto.createHmac('sha256', secretHash)
	.update(timestamp + '.' + body)
	.digest('hex')

await fetch('https://your-host/api/v1/external/messages/text', {
	method: 'POST',
	headers: {
		'Content-Type': 'application/json',
		'X-API-Key': API_KEY,
		'X-Timestamp': timestamp,
		'X-API-Signature': signature,
	},
	body,
})

Endpoints

MethodPathPurpose
POST/messages/textSend a plain text message. Only works inside an open 24-hour conversation window.
POST/messages/templateSend a template, supplying Meta's raw components array yourself.
POST/messages/template/sendSend a template using the mapping you configured in the dashboard. This is the one to use.
POST/messages/interactive/listSend an interactive list message.
POST/messages/interactive/buttonSend up to three reply buttons.
GET/messages/:uuidLook up the delivery status of a message you sent.
POST/media/uploadUpload a PDF/image/video to Meta and get a media_id for use in a template header.
GET/templatesList the templates available to this key.
GET/templates/:nameFetch one template's details.

Sending a mapped template

This is the payload that pairs with the Templates tab. You send variables by NAME — the names shown as chips on the template card — and the platform assembles Meta's components for you. That is the whole point of mapping a template: your billing system does not need to know anything about Meta's component format.

POST /api/v1/external/messages/template/send

{
	"to": "+919876543210",
	"template_name": "payment_failed_alert",
	"language": "en",
	"variables": {
		"customer_name": "Priya Sharma",
		"renewal_date": "12 Aug 2026",
		"plan_name": "Wiz Pro"
	},
	"reference": "invoice-2026-0817"
}

Note: The variable names must match the mapping exactly — an unrecognised name is ignored, and a missing required one is rejected with a 400. reference is yours: it is echoed back on webhooks so you can tie a delivery report to a record in your own system.

Rate limits

Each key carries its own limits, visible on the Overview tab — 60 requests/minute and 1,000 messages/day by default. Responses include the remaining allowance, so back off when it runs low rather than retrying into a wall.