The Macra WhatsApp API lets your business connect a WhatsApp Business number to your own systems, send messages programmatically, and receive inbound messages on a webhook. It sits in front of a managed WhatsApp connection so you never handle QR codes or session state directly. You only talk to a small JSON/HTTPS API.
POST /v1/whatsapp/connect and enter the returned pairing code on the WhatsApp Business phone.POST /v1/messages/send and receive inbound messages on your webhook URL.Every account owes the same one-time onboarding fee — a standard amount set platform-wide by Macra Systems, not negotiated per account. It's separate from the daily message limit and is charged once, not on a recurring schedule.
Pay it from the dashboard, under "Onboarding Fee" — card and mobile money (M-Pesa) are both supported. Payment is processed by Paystack and verified server-side before your account is marked paid. There's no API endpoint for this: it's dashboard-only.
POST /v1/whatsapp/connect returns 402, and the dashboard's "Connect WhatsApp" and "New API Key" actions are disabled.Every request to a /v1/ endpoint must include your API key in an X-Api-Key header. There is no OAuth flow, session, or bearer-token exchange: the key itself is the credential.
X-Api-Key: mwa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys are prefixed mwa_ and are account-scoped: one key authenticates as one account and everything that account is entitled to (its connected WhatsApp number, its message history, its rate limit).
An account can hold multiple active keys at once (for example, one per environment). Create and revoke keys anytime from the dashboard under API Keys; revoking one takes effect immediately.
https://macrasystems.com/wa/v1
All endpoint paths below are relative to this base URL. The API only accepts and returns application/json, and every response, success or error, is a JSON object.
Starts linking your account's verified WhatsApp Business number: the number reviewed during onboarding, shown on your account. On a fresh connection this returns a pairing code you enter on the phone; if the account is already linked and ready, it returns immediately with no pairing step.
402.curl -X POST https://macrasystems.com/wa/v1/whatsapp/connect \ -H "X-Api-Key: mwa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
| Field | Type | Description | |
|---|---|---|---|
| status | string | qr_ready: a pairing code was issued and is waiting to be entered on the phone. ready: the number was already linked; nothing further to do. | |
| pairing_code | string | null | The code to enter on the phone when status is qr_ready. null when status is ready. |
{
"pairing_code": "ABCD-1234",
"status": "qr_ready"
}
{
"pairing_code": null,
"status": "ready"
}
pairing_code. Codes expire after a short window. If it expires before you enter it, call /v1/whatsapp/connect again to get a new one.Returns the current state of your account's WhatsApp connection. Useful for polling after /v1/whatsapp/connect until pairing completes, and before attempting to send.
curl https://macrasystems.com/wa/v1/whatsapp/status \ -H "X-Api-Key: mwa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
| Field | Type | Description | |
|---|---|---|---|
| status | string | One of the four values below. |
{
"status": "connected"
}
Sends a plain-text WhatsApp message from your connected number to any WhatsApp number. Requires the account's connection status to be connected (see Connection Status).
| Field | Type | Description | |
|---|---|---|---|
| to | string | required | Destination number, with country code. Formatting is flexible: spaces and symbols are stripped server-side (e.g. +254 712 345 678 and 254712345678 are equivalent). |
| message | string | required | The text body to send. |
curl -X POST https://macrasystems.com/wa/v1/messages/send \
-H "X-Api-Key: mwa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"to": "254712345678", "message": "Hello from Macra WhatsApp API"}'
{
"id": "true_254712345678@c.us_3EB0...",
"ack": "sent"
}
The response body is the send confirmation from the underlying WhatsApp connection. Its exact fields aren't part of the platform's stable contract, so check for a 200 status to confirm the send succeeded rather than relying on specific field names.
Sends a document (PDF, etc.) from your connected number to any WhatsApp number, with an optional caption. Requires the account's connection status to be connected.
| Field | Type | Description | |
|---|---|---|---|
| to | string | required | Destination number, with country code. Same flexible formatting as Send a Message. |
| document_url | string | required* | A public HTTPS URL the document is fetched from directly. Up to 50MB. *Provide exactly one of document_url or document_base64. |
| document_base64 | string | required* | Raw base64-encoded document data. Limited to ~15MB decoded (larger files: use document_url instead). *Provide exactly one of document_url or document_base64. |
| document_mimetype | string | MIME type of the document, e.g. application/pdf. Defaults to application/pdf when using document_base64. Ignored for document_url. | |
| filename | string | Optional file name shown in WhatsApp. Defaults to a generic name if omitted. | |
| caption | string | Optional caption text sent alongside the document (max 1024 characters). |
curl -X POST https://macrasystems.com/wa/v1/messages/send-document \
-H "X-Api-Key: mwa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"to": "254712345678", "document_base64": "JVBERi0xLjQK...", "document_mimetype": "application/pdf", "filename": "Invoice-1024.pdf", "caption": "Invoice #1024"}'
{
"messageId": "true_254712345678@c.us_3EB0...",
"timestamp": 1719312000
}
To receive WhatsApp messages sent to your connected number, set a webhook URL on your account. Every inbound message is relayed there as an HTTP POST as soon as it arrives.
{
"event": "message_received",
"from": "254712345678",
"message": "Hi, is this order ready?",
"timestamp": 1732012345
}
| Field | Type | Description | |
|---|---|---|---|
| event | string | Always message_received for inbound text messages. | |
| from | string | The sender's WhatsApp number, digits only with country code. | |
| message | string | The message text. | |
| timestamp | integer | Unix timestamp (seconds) of when the message was received. |
Every delivery includes an X-Macra-Signature header so you can confirm it came from Macra Systems and wasn't sent by someone who simply guessed or found your webhook URL:
X-Macra-Signature: sha256=<hex-encoded HMAC-SHA256>
The signature is an HMAC-SHA256 of the raw request body, keyed with your account's webhook signing secret (visible under "Inbound Webhook" in the dashboard, and rotatable there at any time). Recompute it over the exact bytes you received and compare with a constant-time check - don't re-serialize the parsed JSON first, since that isn't guaranteed to produce identical bytes.
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_MACRA_SIGNATURE'] ?? '';
$signature = str_starts_with($header, 'sha256=') ? substr($header, 7) : '';
$expected = hash_hmac('sha256', $raw, $yourWebhookSecret);
if ($signature === '' || !hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
Content-Type: application/json with a 10-second delivery timeout and are not retried on failure, so your endpoint should respond quickly with a 2xx status.X-Macra-Signature before acting on it.Each account has a daily limit on messages sent via /v1/messages/send, counted from midnight and reset daily. The default on the free plan is 100 messages/day. Receiving messages is never rate-limited.
Once the limit is reached, /v1/messages/send returns 429 Daily message limit reached until it resets. There is no rate limit on /v1/whatsapp/connect or /v1/whatsapp/status beyond normal fair use.
Errors are returned as a JSON object with a single detail field describing what went wrong, alongside the HTTP status code.
{
"detail": "Invalid API key"
}
| Status | Meaning | Typical cause |
|---|---|---|
| 400 | Bad Request | Invalid or missing JSON body, a required field is missing, or the WhatsApp connection isn't ready for the action requested. |
| 401 | Unauthorized | The X-Api-Key header is missing or doesn't match a valid key. |
| 402 | Payment Required | The account's one-time onboarding fee hasn't been paid yet. |
| 403 | Forbidden | The API key is valid but the account is suspended. |
| 405 | Method Not Allowed | Wrong HTTP method for the endpoint (e.g. GET on a POST-only endpoint). |
| 429 | Too Many Requests | The account's daily message-sending limit has been reached. |
| 500 | Internal Server Error | An unexpected error on the platform. Safe to retry; contact us if it persists. |
| 502 | Bad Gateway | The underlying WhatsApp connection was unavailable or returned an error. |
| 504 | Gateway Timeout | Timed out waiting on the WhatsApp connection (typically during connect). Safe to retry. |
For questions about your application, API keys, webhook setup, or rate limits, reach out via the contact page and include your account email so we can look you up quickly.