Introduction
Moving from another WhatsApp API provider? See the QaziHost root endpoints, credentials and compatibility status. The reference below describes the existing /api/v1 API.
This platform gives you a simple, secure HTTP API for sending WhatsApp messages from your own website, backend, or automation - using your own WhatsApp number, linked by scanning a QR code. There is a single endpoint, one consistent JSON body for every message type, and API keys that only work from the website domains you allow.
Everything in this reference works from any language or tool that can make an HTTP request - cURL, JavaScript, Node.js, PHP, Python, Google Sheets scripts, Zapier webhooks, anything.
curl -X POST https://your-platform-domain.com/api/v1/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wapi_YOUR_API_KEY" \
-H "Origin: https://yourwebsite.com" \
-d '{
"to": "923001234567",
"type": "text",
"message": "Hello from the API! ๐"
}'Getting started
- Create an account - free, takes under a minute.
- Activate a membership - plans start at Rs 1,500/month. Transfer the fee by bank / Easypaisa / JazzCash, WhatsApp us the screenshot, and submit the payment form in Dashboard โ Membership. An admin activates you, usually within a few hours.
- Connect your WhatsApp number - in the dashboard, add an account and scan the QR with WhatsApp โ Settings โ Linked Devices โ Link a Device.
- Generate an API key - open the account's API Keys tab, list your website domains, and copy the
wapi_โฆkey. - Send your first message with any example on this page. ๐
Authentication
Every request must carry your API key. Two header styles are accepted - use whichever your HTTP client makes easier:
Authorization: Bearer wapi_xxxxxxxxxxxxxxxxxxxxxxxx
# - or -
x-api-key: wapi_xxxxxxxxxxxxxxxxxxxxxxxxKeys are created per WhatsApp account in the dashboard and can be disabled or deleted at any time without affecting your other keys. Requests are additionally checked against the key's origin allowlist.
Send a message
POST /api/v1/send
One endpoint for every message type. The type field selects what you're sending; each type's extra fields are documented below. If you omit type but include message, it defaults to a text message.
Common fields
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient phone number in international format, digits only - no +, spaces, or dashes. Example: 923001234567 |
type | string | Yes* | One of: text, image, video, audio, document, location, contact, poll. (*Optional if you send "message" - defaults to text.) |
Success response - 200
{
"success": true,
"type": "text",
"to": "923001234567@s.whatsapp.net",
"messageId": "3EB0A1B2C3D4E5F6"
}Keep the messageId if you want to correlate delivery/read analytics shown in your dashboard.
Message types
Text
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The text to send. Emoji and WhatsApp formatting (*bold*, _italic_, ~strike~, ```mono```) are supported. |
{
"to": "923001234567",
"type": "text",
"message": "Your order *#1042* has shipped! ๐\nTrack it here: https://example.com/track/1042"
}Image
All media types accept either url (a publicly reachable URL the server downloads) or base64 (raw base64, with or without a data:โฆ;base64, prefix).
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes* | Public image URL (*either url or base64). |
base64 | string | Yes* | Base64-encoded image bytes (*either url or base64). |
caption | string | No | Text shown under the image. |
mimetype | string | No | e.g. image/jpeg - usually auto-detected. |
{
"to": "923001234567",
"type": "image",
"url": "https://example.com/products/shoe.jpg",
"caption": "New arrivals - 20% off this week only!"
}Video
| Field | Type | Required | Description |
|---|---|---|---|
url / base64 | string | Yes | The video source (mp4 recommended). |
caption | string | No | Text under the video. |
gifPlayback | boolean | No | true plays the video like a GIF (muted, looping). |
{
"to": "923001234567",
"type": "video",
"url": "https://example.com/demo.mp4",
"caption": "Watch the 30-second demo ๐ฌ"
}Audio & voice notes
| Field | Type | Required | Description |
|---|---|---|---|
url / base64 | string | Yes | The audio source (mp3 / m4a / ogg). |
ptt | boolean | No | true sends it as a WhatsApp voice note (push-to-talk bubble) instead of an audio file. |
mimetype | string | No | Defaults to audio/mp4. |
{
"to": "923001234567",
"type": "audio",
"url": "https://example.com/greeting.mp3",
"ptt": true
}Document
| Field | Type | Required | Description |
|---|---|---|---|
url / base64 | string | Yes | The file source - PDF, spreadsheet, zip, anything. |
fileName | string | No | The name the recipient sees (default: "file"). |
mimetype | string | No | e.g. application/pdf (default: application/octet-stream). |
caption | string | No | Text under the document. |
{
"to": "923001234567",
"type": "document",
"url": "https://example.com/invoices/1042.pdf",
"fileName": "invoice-1042.pdf",
"mimetype": "application/pdf",
"caption": "Your invoice - thank you for your order!"
}Location
| Field | Type | Required | Description |
|---|---|---|---|
latitude | number | Yes | Decimal latitude. |
longitude | number | Yes | Decimal longitude. |
name | string | No | Place name shown on the pin. |
address | string | No | Address line shown under the name. |
{
"to": "923001234567",
"type": "location",
"latitude": 24.8607,
"longitude": 67.0011,
"name": "Our Store - Karachi",
"address": "Shahrah-e-Faisal, Karachi, Pakistan"
}Contact card
| Field | Type | Required | Description |
|---|---|---|---|
contacts | array | Yes | One or more contacts to share as vCards. |
contacts[].fullName | string | Yes | The contact's display name. |
contacts[].phone | string | Yes | Phone in international format, digits only. |
contacts[].organization | string | No | Company name. |
{
"to": "923001234567",
"type": "contact",
"contacts": [
{
"fullName": "Support Team",
"phone": "923009876543",
"organization": "Your Company"
}
]
}Poll
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The poll question. |
options | string[] | Yes | 2 or more answer options. |
selectableCount | number | No | How many options a voter may pick (default 1). |
{
"to": "923001234567",
"type": "poll",
"name": "What time suits you for delivery?",
"options": ["Morning (9โ12)", "Afternoon (12โ5)", "Evening (5โ9)"],
"selectableCount": 1
}Code examples
Complete, ready-to-paste integrations. Replace the domain, the API key, and the phone number with your own.
cURL
# Text
curl -X POST https://your-platform-domain.com/api/v1/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wapi_YOUR_API_KEY" \
-H "Origin: https://yourwebsite.com" \
-d '{"to":"923001234567","message":"Hello!"}'
# Image with caption
curl -X POST https://your-platform-domain.com/api/v1/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wapi_YOUR_API_KEY" \
-H "Origin: https://yourwebsite.com" \
-d '{"to":"923001234567","type":"image","url":"https://picsum.photos/600","caption":"Hi!"}'JavaScript (browser)
Works from any page served on one of the key's allowed origins - CORS is handled by the API. Remember the key is visible to visitors in this setup; prefer the server-side examples for anything sensitive.
async function sendWhatsApp(to, message) {
const res = await fetch("https://your-platform-domain.com/api/v1/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer wapi_YOUR_API_KEY",
},
body: JSON.stringify({ to, message }),
});
const data = await res.json();
if (!data.success) throw new Error(data.error);
return data;
}
// Example: order confirmation after checkout
document.querySelector("#checkout").addEventListener("submit", async () => {
await sendWhatsApp("923001234567", "โ
We received your order #1042!");
});Node.js (backend)
// Node 18+ - no dependencies needed.
// Keep the key in an environment variable, never in source control.
const API_URL = "https://your-platform-domain.com/api/v1/send";
const API_KEY = process.env.WHATSAPP_API_KEY;
export async function sendWhatsApp(payload) {
const res = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`,
"Origin": "https://yourwebsite.com", // must match an allowed origin
},
body: JSON.stringify(payload),
});
const data = await res.json();
if (!data.success) throw new Error(`QaziHost WhatsApp API: ${data.error}`);
return data;
}
// Usage:
await sendWhatsApp({ to: "923001234567", message: "Hello from Node!" });
await sendWhatsApp({
to: "923001234567",
type: "document",
url: "https://example.com/invoice.pdf",
fileName: "invoice.pdf",
});Next.js API route
// On YOUR website - keeps the key server-side.
export async function POST(req: Request) {
const { to, message } = await req.json();
const res = await fetch("https://your-platform-domain.com/api/v1/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.WHATSAPP_API_KEY}`,
"Origin": "https://yourwebsite.com",
},
body: JSON.stringify({ to, message }),
});
return Response.json(await res.json());
}PHP
<?php
/**
* Reusable helper - works in plain PHP, WordPress, Laravel, anywhere.
*/
function send_whatsapp(array $payload): array {
$ch = curl_init("https://your-platform-domain.com/api/v1/send");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer " . getenv("WHATSAPP_API_KEY"),
"Origin: https://yourwebsite.com",
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($response["success"])) {
throw new Exception("QaziHost WhatsApp API: " . ($response["error"] ?? "unknown error"));
}
return $response;
}
// Text message
send_whatsapp([
"to" => "923001234567",
"message" => "Your order #1042 has shipped!",
]);
// Image
send_whatsapp([
"to" => "923001234567",
"type" => "image",
"url" => "https://example.com/receipt.png",
"caption" => "Payment received โ
",
]);Python
import os
import requests
API_URL = "https://your-platform-domain.com/api/v1/send"
API_KEY = os.environ["WHATSAPP_API_KEY"]
def send_whatsapp(payload: dict) -> dict:
"""Send any message type. Raises on failure."""
res = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Origin": "https://yourwebsite.com",
},
json=payload,
timeout=30,
)
data = res.json()
if not data.get("success"):
raise RuntimeError(f"QaziHost WhatsApp API: {data.get('error')}")
return data
# Text
send_whatsapp({"to": "923001234567", "message": "Hello from Python!"})
# Poll
send_whatsapp({
"to": "923001234567",
"type": "poll",
"name": "Pick a delivery slot",
"options": ["Morning", "Afternoon", "Evening"],
})Incoming webhooks
Get every message your WhatsApp number receives POSTed to your server - replies, order confirmations, support requests. Enable it per number in Dashboard โ Settings โ Incoming message webhook by entering your URL (and, ideally, a signing secret).
Payload
{
"event": "message.received",
"accountId": "5f2cโฆ",
"accountName": "Sales line",
"from": "923001234567@s.whatsapp.net",
"fromName": "Ali Khan",
"isGroup": false,
"messageId": "3EB0A1B2C3D4",
"timestamp": 1754630000000,
"text": "Do you deliver to Lahore?"
}Verifying the signature
When a secret is set, every request carries an X-Webhook-Signature header: the hex HMAC-SHA256 of the raw request body. Reject anything that doesn't match:
import crypto from "crypto";
export function verifyWebhook(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader ?? "")
);
}Deliveries time out after 10 seconds and are not retried - respond with a quick 200 and process the message asynchronously.
Error reference
Every error is JSON with success: false and a human-readable error string. Check the HTTP status to branch your handling:
| Status | Meaning | What to do |
|---|---|---|
400 | Invalid request body - missing to, bad media source, etc. | Fix the payload; the error message says exactly what's wrong. |
401 | Missing or invalid API key. | Check the Authorization header and the key value. |
402 | Membership expired, or the monthly message quota is used up. | Renew or upgrade in Dashboard โ Membership; sending resumes instantly. |
403 | Key disabled, or the calling origin isn't in the key's allowlist. | Enable the key / add your domain in the API Keys tab. |
503 | Your WhatsApp number is not connected. | Open the Connection tab and re-scan the QR code. |
500 | Unexpected server error. | Retry with backoff; contact support if it persists. |
{
"success": false,
"error": "Origin 'evil.example' is not allowed for this API key."
}Origin allowlist
Every API key carries a list of website hosts allowed to use it. The server checks the request's Origin (or Referer) header against that list:
| Pattern | Matches |
|---|---|
example.com | Exactly example.com |
*.example.com | Any subdomain plus the bare domain (shop.example.com, example.comโฆ) |
https://example.com/path | Accepted when saving - only the host part is stored |
| (empty list) | Any origin may use the key - not recommended |
Server-to-server callers should set the Origin header manually (see the Node/PHP/Python examples). Note that non-browser clients can forge this header - the allowlist is a guardrail for browser usage, while the API key itself is the primary credential.
Platform plugins
You don't have to write code to use this API. Ready-made plugins connect popular platforms in a few minutes - each needs only your platform URL and an API key. Logged-in members will find download links and illustrated setup guides in Dashboard โ Integrations.
WordPress / WooCommerce
One plugin covers both. On any WordPress site it gives you a settings page, a test sender, and a PHP helper (wa_api_send_message($to, $message)) for developers. When WooCommerce is active it also sends:
- Customer order notifications on status changes (processing, on-hold, completed, cancelled, refunded, failed) to the order's billing phone - each status has its own toggle and editable template.
- Admin new-order alerts to your own WhatsApp number.
Install: Plugins โ Add New โ Upload Plugin, upload the zip, activate, then open Settings โ QaziHost WhatsApp API and paste your API URL + key. Templates support {first_name}, {order_number}, {order_total}, {order_items}, {order_url}, {site_name} and more. Phone numbers starting with 0 are converted using the default country code you set (e.g. 0300โฆ becomes 92300โฆ).
WHMCS
An addon module for hosting companies and agencies. Upload the whatsappapi folder to modules/addons/, activate it under System Settings โ Addon Modules, and configure. It notifies clients on:
- Invoice created and invoice paid (with amount, due date, and a payment link)
- Staff replies to their support ticket
- Service activation, and a welcome message on signup
- Plus an admin alert for every new order
Every notification has its own toggle and template (placeholders like {invoice_id}, {amount}, {invoice_url}). Client numbers are read from their WHMCS profile automatically, and failures are logged to the WHMCS Activity Log.
Tip for both plugins: create a separate API key per website in the dashboard, and give each a daily limit - if a store misbehaves you can revoke just that key. The plugins send your site's own URL as the Referer, so keys restricted to that domain keep working for server-side sends.
Plans & quotas
Your membership decides how many WhatsApp numbers you can connect and how many messages you can send per calendar month (see pricing). Limits are enforced automatically:
- Quota counts successful sends in the current calendar month across all your numbers and keys - API, scheduled, and AI replies included.
- Hitting the quota (or letting the plan expire) returns
402from the API. Nothing is deleted - renew and sending resumes instantly. - Your live usage is always visible in Dashboard โ Membership.
- Renewing the same plan before expiry extends your current end date - you never lose paid days.
Best practices
- Call from your backend. Keys in browser JavaScript are visible to visitors; the allowlist limits abuse but server-side is safer.
- Keep throttling on. The per-account throttle (Settings tab) paces sends like a human and protects your number from bans.
- Message people who expect it. Order updates, OTPs, replies - not cold blasts. Recipient "report spam" taps are the fastest route to a ban.
- Use a dedicated number, not your personal WhatsApp, and warm new numbers up gradually.
- Handle errors. Retry
503after reconnecting, surface402to whoever manages billing, and log400payload issues. - Rotate keys if you suspect a leak - generate a new key, deploy it, then delete the old one (zero downtime).
Support
Stuck? Message us on WhatsApp (the same number shown on the homepage payment section) with your registered email, and include the full JSON error response if your question is about the API. Paid members get priority support.