Gaptix WhatsApp API
Send WhatsApp messages from your website, app or CRM, keep contacts in sync and get customer replies in real time — with one simple JSON API on top of the official WhatsApp Business Platform.
- Connect WhatsApp in your Gaptix dashboard (Settings).
- Create an API key in Developer → API keys. It starts with
wak_. - Call the API — try status first, then send a template.
Authentication
Send your API key with every request in the X-API-Key header. Authorization: Bearer wak_… also works. Keep keys secret — use them only from your server, never in a browser or mobile app.
Each key can be limited to your server’s IP addresses and switched off at any time in Developer → API keys.
Request format
Pick the action with ?action=… (or the path, e.g. /api.php/send_text). Send parameters as JSON with Content-Type: application/json, as form fields, or — for GET actions — in the query string.
curl "https://gaptix.com/api.php?action=status" \ -H "X-API-Key: wak_your_api_key"
Responses & errors
Every response is JSON with success and a request_id (also in the X-Request-Id header — quote it when contacting support). Errors carry a human-readable error and a stable error_code you can branch on.
| HTTP | error_code | Meaning |
|---|---|---|
| 400 | invalid_json / missing_action | The request could not be read |
| 401 | unauthorized | Missing, wrong or unknown API key |
| 402 | insufficient_balance | Wallet too low to open a new conversation — recharge |
| 403 | key_disabled / ip_not_allowed / api_disabled | Key switched off, IP not on the key’s allow-list, or API disabled for the account |
| 404 | unknown_action / not_found / template_not_found | The action, message, contact or template doesn’t exist |
| 409 | duplicate | A contact with this phone already exists |
| 422 | missing_parameter, invalid_phone, template_param_mismatch, missing_header, … | A parameter is missing or invalid — the message says exactly what to fix |
| 429 | rate_limited | More than 120 requests a minute — wait for Retry-After seconds |
| 502 | meta_error, outside_24h_window, undeliverable, … | WhatsApp refused the message. meta_code holds WhatsApp’s own error code |
| 503 | whatsapp_not_connected / token_expired | Your WhatsApp number isn’t connected or its token expired |
{
"success": false,
"error": "Template 'order_confirmed' needs 3 variable(s) but 2 were given.",
"error_code": "template_param_mismatch",
"request_id": "req_8f2c1a9d4b7e6031"
}Limits & billing
- Rate limit: 120 requests per minute per key. Every response has
X-RateLimit-Remaining; over the limit you get429withRetry-After. - 24-hour window: free-form messages (text, media, buttons, lists…) can only be sent within 24 hours of the customer’s last message. Outside it, send an approved template.
- Billing: sends are checked against your wallet first (
402 insufficient_balance). You are charged per 24-hour conversation only when WhatsApp confirms delivery — failed messages cost nothing. - Messaging limit: how many new customers you can start conversations with per day is set by WhatsApp (see WhatsApp Health in the dashboard).
- Messages sent through the API also appear in your Live Chat inbox and chat history.
Webhook events
Register a URL with register_webhook (or in Developer → Webhooks). We send a POST with a JSON body for each event and the header X-Gaptix-Event. Reply with any 2xx within 10 seconds.
| message | A customer sent you a message (text, button reply, list reply, media…). |
| message.status | A message you sent changed status: sent, delivered, read or failed (with error). |
Verifying signatures
If you set a secret, each request has X-WA-Signature: sha256=<hex> — an HMAC-SHA256 of the raw body using your secret. Compute it yourself and compare before trusting the event.
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_WA_SIGNATURE'] ?? '';
$ok = hash_equals('sha256=' . hash_hmac('sha256', $body, 'your-secret'), $sig);
if (!$ok) { http_response_code(401); exit; }
$event = json_decode($body, true);{
"event": "message",
"from": "919876543210",
"contact_name": "Riya Sharma",
"type": "text",
"text": "Is it available in blue?",
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAEhgU",
"phone_number_id": "1314759468377345",
"timestamp": 1790237113
}{
"event": "message.status",
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS",
"status": "delivered",
"to": "919876543210",
"timestamp": 1790237066,
"phone_number_id": "1314759468377345",
"error": null
}Account status
Checks your API key and WhatsApp connection. Returns the connected number, quality rating and wallet balance. Good first call to test your setup.
No parameters.
{
"success": true,
"status": "connected",
"account": {
"phone_number": "+91 98765 43210",
"phone_number_id": "1314759468377345",
"verified_name": "Pitch Point",
"phone_status": "CONNECTED",
"quality_rating": "GREEN",
"throughput": "STANDARD",
"waba_id": "2011356992820445"
},
"wallet": {
"balance": 499.65,
"currency": "INR"
},
"api_key": {
"name": "Production",
"requests_today": 42,
"requests_total": 1830
},
"request_id": "req_8f2c1a9d4b7e6031"
}Wallet balance
Your WhatsApp wallet: balance, money on hold for messages not yet delivered, and your per-conversation rates.
No parameters.
{
"success": true,
"wallet": {
"balance": 499.65,
"currency": "INR",
"billing_mode": "prepaid",
"on_hold": 0.15,
"awaiting_delivery": 3,
"rates_per_conversation": {
"marketing": 0.1,
"utility": 0.05,
"authentication": 0.05,
"service": 0.05
},
"free_service_per_month": 1000
},
"request_id": "req_8f2c1a9d4b7e6031"
}Send a template message
Sends an approved WhatsApp template. Works any time — including outside the 24-hour window. Aliases: send_template.
Parameters
| torequiredstring | Recipient number with country code, digits only — e.g. 919876543210 |
| template_namerequiredstring | Name of an APPROVED template |
| languagestring | Template language code, e.g. en_US. Defaults to the approved version. |
| variablesarray | object | Body variables in order: ["Riya","ORD-1042"]. For templates with named variables pass an object: {"name":"Riya"}. |
| headerobject | Required when the template has a media or variable header. {"link":"https://…/photo.jpg"}, {"id":"<media_id>"}, {"text":"…"}, or for documents add "filename". |
| button_url_valuestring | Value for the {{1}} at the end of a dynamic website button (first one). |
| buttonsarray | Per-button values: [{"index":0,"value":"ORD-1042"}]. Also for coupon_code buttons, quick-reply payload and flow flow_token. |
| coupon_codestring | Shortcut for templates with a copy-code button. |
| componentsarray | Advanced: Meta components array sent as-is (e.g. carousel templates). |
| reply_tostring | Message id to quote/reply to. |
variables: ["482913"] — it is placed in the body and the copy-code button automatically.{
"success": true,
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS",
"to": "919876543210",
"template": "order_confirmed",
"language": "en_US",
"category": "utility",
"request_id": "req_8f2c1a9d4b7e6031"
}Send a text message
Free-form text. Only allowed within 24 hours of the customer’s last message to you (the customer service window).
Parameters
| torequiredstring | Recipient number |
| messagerequiredstring | Up to 4096 characters. Supports *bold*, _italic_, ~strike~. |
| preview_urlboolean | Show a link preview for the first URL |
| reply_tostring | Message id to reply to |
{
"success": true,
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS",
"to": "919876543210",
"request_id": "req_8f2c1a9d4b7e6031"
}Send image, video, audio or document
Send a file by public URL or by a media_id from upload_media. 24-hour window applies.
Parameters
| torequiredstring | Recipient number |
| typerequiredstring | image, video, audio, document or sticker |
| linkstring | Public https URL of the file |
| media_idstring | Id returned by upload_media (use instead of link) |
| captionstring | For image, video, document |
| filenamestring | Shown for documents |
{
"success": true,
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS",
"to": "919876543210",
"type": "document",
"request_id": "req_8f2c1a9d4b7e6031"
}Send buttons, a list or a link button
Interactive messages customers can tap. 24-hour window applies.
Parameters
| torequiredstring | Recipient number |
| typerequiredstring | buttons (up to 3 reply buttons), list (menu of up to 10 rows) or cta_url (one link button) |
| bodyrequiredstring | Message text |
| headerstring | object | Text, or {"type":"image","link":"…"} |
| footerstring | Small grey text |
| buttonsarray | For buttons: [{"id":"yes","title":"Yes"}] — titles up to 20 characters |
| sectionsarray | For list: [{"title":"…","rows":[{"id":"r1","title":"…","description":"…"}]}] |
| buttonstring | List menu label, or the link button text for cta_url |
| urlstring | For cta_url |
message webhook with type: "interactive" and the button id.{
"success": true,
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS",
"to": "919876543210",
"type": "buttons",
"request_id": "req_8f2c1a9d4b7e6031"
}Send a location
A map pin.
Parameters
| torequiredstring | Recipient number |
| latituderequirednumber | |
| longituderequirednumber | |
| namestring | Place name |
| addressstring | Address line |
{
"success": true,
"message_id": "wamid.HBgM…",
"to": "919876543210",
"request_id": "req_8f2c1a9d4b7e6031"
}Send contact cards
Share one or more contact cards.
Parameters
| torequiredstring | Recipient number |
| contactsrequiredarray | [{"name":"Asha Rao","phone":"919812345678","email":"…","company":"…"}] |
{
"success": true,
"message_id": "wamid.HBgM…",
"to": "919876543210",
"request_id": "req_8f2c1a9d4b7e6031"
}React to a message
Adds an emoji reaction to a message in the chat. Pass an empty emoji to remove it.
Parameters
| torequiredstring | Recipient number |
| message_idrequiredstring | Message to react to |
| emojistring | Default 👍 |
{
"success": true,
"message_id": "wamid.HBgM…",
"to": "919876543210",
"request_id": "req_8f2c1a9d4b7e6031"
}Mark as read
Shows blue ticks for an incoming message. Optionally show “typing…” while you prepare a reply.
Parameters
| message_idrequiredstring | Id of the incoming message |
| typingboolean | Also show a typing indicator (up to 25 seconds) |
{
"success": true,
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAEhgU",
"read": true,
"request_id": "req_8f2c1a9d4b7e6031"
}Send a template to many numbers
Up to 1,000 recipients per call. Each recipient can have its own variables. Numbers that fail are reported individually — the rest still send.
Parameters
| template_namerequiredstring | Approved template |
| recipientsrequiredarray | ["9198…","9197…"] or [{"to":"9198…","variables":["Riya"]}] |
| variablesarray | Shared variables for plain-number recipients |
| languagestring | |
| header / buttons / button_url_value | Same as send_message (shared, or per recipient) |
{
"success": true,
"template": "diwali_offer",
"total": 2,
"sent": 2,
"failed": 0,
"results": [
{
"to": "919876543210",
"success": true,
"message_id": "wamid.HBgM…"
},
{
"to": "919812345678",
"success": true,
"message_id": "wamid.HBgM…"
}
],
"request_id": "req_8f2c1a9d4b7e6031"
}Message status
Delivery status of a message you sent: sent → delivered → read, or failed with the reason. Also shows whether it has been billed.
Parameters
| message_idrequiredstring | The id returned when you sent it |
message.status webhook for real-time updates instead of polling.{
"success": true,
"message": {
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS",
"to": "919876543210",
"direction": "out",
"type": "template",
"template": "order_confirmed",
"status": "delivered",
"error": null,
"sent_at": "2026-09-24 10:21:04",
"updated_at": "2026-09-24 10:21:06",
"billing": {
"state": "charged",
"charged": 0.05
}
},
"request_id": "req_8f2c1a9d4b7e6031"
}Chat history with a number
Messages exchanged with one customer, newest first (incoming and outgoing, from every channel — API, Live Chat, bots, campaigns).
Parameters
| phonerequiredstring | Customer number |
| limitinteger | 1–100, default 50 |
| before_idinteger | For paging: pass next_before_id from the previous page |
{
"success": true,
"phone": "919876543210",
"messages": [
{
"id": 561,
"message_id": "wamid.HBgM…",
"direction": "in",
"type": "text",
"text": "Is it available in blue?",
"status": "received",
"created_at": "2026-09-24 10:25:13"
}
],
"count": 1,
"next_before_id": 561,
"request_id": "req_8f2c1a9d4b7e6031"
}List conversations
Everyone you have chatted with, most recent first, with the last message and unread count.
Parameters
| limitinteger | 1–100, default 50 |
| pageinteger | Default 1 |
{
"success": true,
"conversations": [
{
"phone": "919876543210",
"name": "Riya Sharma",
"last_message": "Is it available in blue?",
"last_direction": "in",
"last_message_at": "2026-09-24 10:25:13",
"unread": 1,
"total_messages": 14
}
],
"page": 1,
"limit": 20,
"request_id": "req_8f2c1a9d4b7e6031"
}Upload media
Uploads a file to WhatsApp and returns a media_id you can reuse for 30 days in send_media or template headers — faster than sending a link each time.
Parameters
| filefile | Multipart form field (send as multipart/form-data) |
| urlstring | Or a public URL for us to fetch the file from |
{
"success": true,
"media_id": "1234567890123456",
"mime_type": "application/pdf",
"size": 482113,
"expires_in_days": 30,
"request_id": "req_8f2c1a9d4b7e6031"
}Get media URL
Returns a short-lived download URL for a media id you received in an incoming message webhook.
Parameters
| media_idrequiredstring |
Authorization header.{
"success": true,
"media": {
"media_id": "1234567890123456",
"url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=…",
"mime_type": "image/jpeg",
"size": 84213,
"sha256": "…"
},
"request_id": "req_8f2c1a9d4b7e6031"
}List templates
All message templates on your WhatsApp account, with the variables each one needs. Alias: list_templates.
Parameters
| statusstring | Filter: APPROVED, PENDING, REJECTED, PAUSED |
| categorystring | Filter: MARKETING, UTILITY, AUTHENTICATION |
{
"success": true,
"templates": [
{
"id": "11",
"name": "order_confirmed",
"status": "APPROVED",
"category": "UTILITY",
"language": "en_US",
"variables": {
"header": [],
"body": [
"1",
"2",
"3"
]
},
"components": [
"…"
],
"quality": "GREEN",
"rejected_reason": null
}
],
"total_count": 1,
"approved_count": 1,
"request_id": "req_8f2c1a9d4b7e6031"
}Get one template
Details of a single template.
Parameters
| template_namerequiredstring | |
| languagestring |
{
"success": true,
"template": {
"id": "11",
"name": "order_confirmed",
"status": "APPROVED",
"category": "UTILITY",
"language": "en_US",
"variables": {
"body": [
"1",
"2",
"3"
]
},
"components": [
"…"
]
},
"request_id": "req_8f2c1a9d4b7e6031"
}Create a template
Submits a new template to Meta for review. Use the simple fields, or pass Meta’s full components array for headers with media, carousels etc.
Parameters
| namerequiredstring | Lowercase letters, numbers, underscores |
| categoryrequiredstring | MARKETING, UTILITY or AUTHENTICATION |
| languagestring | Default en_US |
| bodystring | Body text with {{1}} variables |
| examplesarray | One sample value per variable (required by Meta) |
| headerstring | Text header |
| footerstring | |
| buttonsarray | Meta button objects, e.g. [{"type":"QUICK_REPLY","text":"Yes"}] |
| componentsarray | Full Meta components (overrides the simple fields) |
{
"success": true,
"template": {
"id": "1098765432109876",
"name": "appointment_reminder",
"status": "PENDING",
"category": "UTILITY",
"language": "en_US"
},
"request_id": "req_8f2c1a9d4b7e6031"
}Delete a template
Deletes a template (all languages, or one version with template_id).
Parameters
| template_namerequiredstring | |
| template_idstring | Delete only this language version |
{
"success": true,
"deleted": true,
"template_name": "appointment_reminder",
"request_id": "req_8f2c1a9d4b7e6031"
}Create a contact
Adds a lead to your CRM. Team routing rules (round robin, city, state…) assign it automatically.
Parameters
| phonestring | Phone or email is required |
| emailstring | |
| namestring | |
| companystring | |
| statusstring | new, contacted, qualified, won, lost |
| tagsarray | string | ["vip","diwali"] |
| city / state / countrystring | |
| notesstring | |
| sourcestring | Default api |
| upsertboolean | Update the contact if the phone already exists |
201. A duplicate phone returns 409 duplicate unless upsert is true.{
"success": true,
"contact": {
"id": 1520,
"name": "Riya Sharma",
"phone": "919876543210",
"email": "riya@example.com",
"company": "",
"status": "new",
"source": "api",
"tags": [
"website",
"hot"
],
"city": "Jaipur",
"state": null,
"country": null,
"notes": null,
"assigned_to": 3,
"created_at": "2026-09-24 10:30:00",
"updated_at": "2026-09-24 10:30:00"
},
"request_id": "req_8f2c1a9d4b7e6031"
}List contacts
Search and page through your CRM contacts.
Parameters
| searchstring | Matches name, phone, email, company |
| statusstring | |
| tagstring | |
| updated_sincestring | Date/time, e.g. 2026-09-01 |
| limitinteger | 1–200, default 50 |
| pageinteger |
{
"success": true,
"contacts": [
{
"id": 1520,
"name": "Riya Sharma",
"phone": "919876543210",
"status": "new",
"tags": [
"website",
"hot"
]
}
],
"total": 1,
"page": 1,
"limit": 50,
"pages": 1,
"request_id": "req_8f2c1a9d4b7e6031"
}Get a contact
One contact by id or phone.
Parameters
| idinteger | |
| phonestring |
{
"success": true,
"contact": {
"id": 1520,
"name": "Riya Sharma",
"phone": "919876543210",
"…": "…"
},
"request_id": "req_8f2c1a9d4b7e6031"
}Update a contact
Change any field. Tags can be replaced, added to or removed.
Parameters
| id / phonerequired | Which contact |
| any contact field | name, email, status, tags, city… |
| tags_modestring | replace (default), add or remove |
{
"success": true,
"contact": {
"id": 1520,
"status": "qualified",
"tags": [
"website",
"hot",
"paid"
],
"…": "…"
},
"request_id": "req_8f2c1a9d4b7e6031"
}Delete a contact
Permanently removes a contact from the CRM.
Parameters
| id / phonerequired |
{
"success": true,
"deleted": true,
"id": 1520,
"request_id": "req_8f2c1a9d4b7e6031"
}Register a webhook
We POST events to your URL in real time: incoming customer messages and delivery status updates.
Parameters
| urlrequiredstring | Your https endpoint |
| eventsarray | message (default) and/or message.status |
| secretstring | Used to sign each request — see verifying signatures |
| labelstring | A name for your reference |
{
"success": true,
"webhook_id": 12,
"url": "https://yourapp.com/whatsapp/webhook",
"label": "My CRM",
"events": [
"message",
"message.status"
],
"secret_set": true,
"request_id": "req_8f2c1a9d4b7e6031"
}List webhooks
Your webhooks with their last delivery result.
No parameters.
{
"success": true,
"webhooks": [
{
"id": 12,
"label": "My CRM",
"url": "https://yourapp.com/whatsapp/webhook",
"events": [
"message",
"message.status"
],
"is_active": true,
"last_called_at": "2026-09-24 10:25:13",
"last_status": 200,
"fail_count": 0,
"secret_set": true
}
],
"count": 1,
"request_id": "req_8f2c1a9d4b7e6031"
}Send a test event
Sends a sample message event to your webhook and tells you what your server answered.
Parameters
| webhook_idrequiredinteger |
{
"success": true,
"delivered": true,
"http_status": 200,
"error": null,
"response_preview": "OK",
"request_id": "req_8f2c1a9d4b7e6031"
}Delete a webhook
Stops sending events to that URL.
Parameters
| webhook_idrequiredinteger |
{
"success": true,
"deleted": true,
"webhook_id": 12,
"request_id": "req_8f2c1a9d4b7e6031"
}Changelog
v2 · Sep 2026 — media, interactive, location, contacts, reactions, read receipts, bulk sends, message status & history, conversations, templates create/delete, CRM contacts, message.status webhooks, rate-limit headers, stable error_codes. All v1 actions (send_message, send_text, get_templates, status, webhooks) keep working unchanged.
© 2026 Gaptix. WhatsApp is a trademark of Meta Platforms, Inc.