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

  1. Create an account - free, takes under a minute.
  2. 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.
  3. Connect your WhatsApp number - in the dashboard, add an account and scan the QR with WhatsApp โ†’ Settings โ†’ Linked Devices โ†’ Link a Device.
  4. Generate an API key - open the account's API Keys tab, list your website domains, and copy the wapi_โ€ฆ key.
  5. Send your first message with any example on this page. ๐ŸŽ‰
Base URL: all endpoints in this reference are relative to the domain where this platform is hosted - the same domain you're reading these docs on.

Authentication

Every request must carry your API key. Two header styles are accepted - use whichever your HTTP client makes easier:

request headers
Authorization: Bearer wapi_xxxxxxxxxxxxxxxxxxxxxxxx
# - or -
x-api-key: wapi_xxxxxxxxxxxxxxxxxxxxxxxx

Keys 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.

โš ๏ธ Treat your API key like a password. Anyone who has it can send messages from your WhatsApp number (from allowed origins). Prefer calling the API from your backend so the key never ships to visitors' browsers.

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

FieldTypeRequiredDescription
tostringYesRecipient phone number in international format, digits only - no +, spaces, or dashes. Example: 923001234567
typestringYes*One of: text, image, video, audio, document, location, contact, poll. (*Optional if you send "message" - defaults to text.)

Success response - 200

response body
{
  "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

FieldTypeRequiredDescription
messagestringYesThe text to send. Emoji and WhatsApp formatting (*bold*, _italic_, ~strike~, ```mono```) are supported.
POST /api/v1/send
{
  "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).

FieldTypeRequiredDescription
urlstringYes*Public image URL (*either url or base64).
base64stringYes*Base64-encoded image bytes (*either url or base64).
captionstringNoText shown under the image.
mimetypestringNoe.g. image/jpeg - usually auto-detected.
POST /api/v1/send
{
  "to": "923001234567",
  "type": "image",
  "url": "https://example.com/products/shoe.jpg",
  "caption": "New arrivals - 20% off this week only!"
}

Video

FieldTypeRequiredDescription
url / base64stringYesThe video source (mp4 recommended).
captionstringNoText under the video.
gifPlaybackbooleanNotrue plays the video like a GIF (muted, looping).
POST /api/v1/send
{
  "to": "923001234567",
  "type": "video",
  "url": "https://example.com/demo.mp4",
  "caption": "Watch the 30-second demo ๐ŸŽฌ"
}

Audio & voice notes

FieldTypeRequiredDescription
url / base64stringYesThe audio source (mp3 / m4a / ogg).
pttbooleanNotrue sends it as a WhatsApp voice note (push-to-talk bubble) instead of an audio file.
mimetypestringNoDefaults to audio/mp4.
POST /api/v1/send
{
  "to": "923001234567",
  "type": "audio",
  "url": "https://example.com/greeting.mp3",
  "ptt": true
}

Document

FieldTypeRequiredDescription
url / base64stringYesThe file source - PDF, spreadsheet, zip, anything.
fileNamestringNoThe name the recipient sees (default: "file").
mimetypestringNoe.g. application/pdf (default: application/octet-stream).
captionstringNoText under the document.
POST /api/v1/send
{
  "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

FieldTypeRequiredDescription
latitudenumberYesDecimal latitude.
longitudenumberYesDecimal longitude.
namestringNoPlace name shown on the pin.
addressstringNoAddress line shown under the name.
POST /api/v1/send
{
  "to": "923001234567",
  "type": "location",
  "latitude": 24.8607,
  "longitude": 67.0011,
  "name": "Our Store - Karachi",
  "address": "Shahrah-e-Faisal, Karachi, Pakistan"
}

Contact card

FieldTypeRequiredDescription
contactsarrayYesOne or more contacts to share as vCards.
contacts[].fullNamestringYesThe contact's display name.
contacts[].phonestringYesPhone in international format, digits only.
contacts[].organizationstringNoCompany name.
POST /api/v1/send
{
  "to": "923001234567",
  "type": "contact",
  "contacts": [
    {
      "fullName": "Support Team",
      "phone": "923009876543",
      "organization": "Your Company"
    }
  ]
}

Poll

FieldTypeRequiredDescription
namestringYesThe poll question.
optionsstring[]Yes2 or more answer options.
selectableCountnumberNoHow many options a voter may pick (default 1).
POST /api/v1/send
{
  "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

terminal
# 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.

script.js
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)

whatsapp.js
// 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

app/api/notify/route.ts
// 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

send-whatsapp.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

whatsapp.py
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

POST <your webhook URL>
{
  "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:

verify.js (Node)
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:

StatusMeaningWhat to do
400Invalid request body - missing to, bad media source, etc.Fix the payload; the error message says exactly what's wrong.
401Missing or invalid API key.Check the Authorization header and the key value.
402Membership expired, or the monthly message quota is used up.Renew or upgrade in Dashboard โ†’ Membership; sending resumes instantly.
403Key disabled, or the calling origin isn't in the key's allowlist.Enable the key / add your domain in the API Keys tab.
503Your WhatsApp number is not connected.Open the Connection tab and re-scan the QR code.
500Unexpected server error.Retry with backoff; contact support if it persists.
error response example
{
  "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:

PatternMatches
example.comExactly example.com
*.example.comAny subdomain plus the bare domain (shop.example.com, example.comโ€ฆ)
https://example.com/pathAccepted 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 402 from 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 503 after reconnecting, surface 402 to whoever manages billing, and log 400 payload 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.