Since 1998 5000+ Clients 99.9% Uptime TRAI Certified
💬 DEVELOPER GUIDE · WHATSAPP + PHP

Send WhatsApp Messages Using PHP

Working PHP for the official WhatsApp Business API: approved templates with variables, PDF and image headers, reply buttons inside the 24-hour window, error handling, and what each message costs in India after Meta's October 2026 changes. At the end, the same send made directly against Meta's Cloud API.

📅 Updated 29 Oct 2026⏱️ 11 min read🧪 PHP 8.0+ with cURL

To send a WhatsApp message from PHP, POST the recipient number, an approved template name and its parameters to a WhatsApp Business API endpoint with your API key. With the AvanceZone API that is one curl_init() call to /api/v2/whatsapp/send. Free-form text is only allowed within 24 hours of the customer's last message; at any other time WhatsApp requires a pre-approved template. Each delivered message is billed by category, so choose templates carefully.

1. The two rules that shape your code

  • Templates outside the window. You may send free-form messages only within 24 hours of the customer's last message to you. Any other time, including the first contact, the message must be a template Meta has approved, with only its variables changing.
  • Every template has a category. Marketing, utility or authentication. Meta decides the category when it approves the template, and the category sets the price.

So the first job is getting templates approved, not writing code. Your WhatsApp Business account needs to be connected and enabled on the platform. As a Meta Tech Provider, we handle number setup and template submission during onboarding.

2. Send an approved template

Suppose Meta has approved a utility template called order_shipped with the body "Hi {{1}}, your order {{2}} has been shipped. Track it here: {{3}}". Fill the placeholders in order through params:

PHPsend-whatsapp.php
<?php
// send-whatsapp.php: run with  WA_API_KEY=xxxx php send-whatsapp.php
$payload = [
    'to'       => '919876543210',             // country code + number
    'template' => 'order_shipped',            // approved template name
    'language' => 'en',                       // the language it was approved in
    'params'   => ['Priya', 'A1023', 'https://avz.in/t/9KX2'],   // {{1}}, {{2}}, {{3}}
];

$ch = curl_init('https://whitelabled.securesmsc.com/api/v2/whatsapp/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT        => 20,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('WA_API_KEY'),
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload),
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = json_decode((string) $body, true);
if ($http === 200 && ($res['status'] ?? '') === 'success') {
    echo "Sent: {$res['data']['message_id']}\n";       // a WhatsApp message id (wamid...)
} else {
    echo "Failed ($http): " . ($res['code'] ?? '') . ' ' . ($res['message'] ?? '') . "\n";
}

A successful response looks like this. Store message_id; you can later ask GET /api/v2/whatsapp/status/{messageId} for its delivery status.

JSONHTTP 200
{
  "status": "success",
  "data": {
    "message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSQzVGRDY0",
    "id": 5521,
    "to": "919876543210"
  }
}

Up to 20 parameters are accepted. Send them as strings in the exact order of the template's placeholders. If a template exists in more than one language, pass language (for example ta for Tamil); otherwise the approved version is used.

3. Template with a PDF or image header

Invoices, tickets and lab reports usually go out as a utility template approved with a document header. Add the header fields to the same request:

PHP
<?php
$payload = [
    'to'                => '919876543210',
    'template'          => 'invoice_ready',
    'params'            => ['Priya', 'INV-2026-0412', '4,720'],
    'header_media_url'  => 'https://files.example.com/invoices/INV-2026-0412.pdf',
    'header_media_type' => 'document',              // or 'image' / 'video'
    'header_filename'   => 'Invoice-INV-2026-0412.pdf',
];

The file must be reachable over public HTTPS when the message is sent, because WhatsApp fetches it. Use a signed URL that stays valid for a few hours rather than exposing a permanent public folder. If the template was approved with a media header, the header fields are required; Meta rejects the send without them.

4. Text and reply buttons inside the 24-hour window

When a customer has written to you within the last 24 hours, you can reply with plain text, media, or text with up to three reply buttons. Each button title can be at most 20 characters:

PHP
<?php
$payload = [
    'to'      => '919876543210',
    'message' => 'Your appointment is tomorrow at 4:30 PM. Can you make it?',
    'buttons' => [
        ['id' => 'appt_yes',        'title' => 'Yes, confirm'],
        ['id' => 'appt_reschedule', 'title' => 'Reschedule'],
        ['id' => 'appt_cancel',     'title' => 'Cancel'],
    ],
];
// Outside the window this returns 422 with code "template_required".

Two constraints: buttons only work with a free-form message (not with a template or media), and on our shared platform number only approved templates can be sent. Free-form replies need your own connected number. Replies inside the window are service messages: the first 1,000 a month per business number are free, and since 1 October 2026 the rest are billed.

5. Handle the errors

Errors come back as {"status":"error","message":"...","code":"..."}. These are the ones worth handling explicitly:

HTTP / codeMeaningWhat to do
422 template_requiredThe 24-hour window is closedSend an approved template instead
422 shared_number_templates_onlyFree-form text on the shared platform numberUse a template, or connect your own number
422 invalid_recipientNumber missing or malformedStore numbers with country code; fall back to SMS
422 buttons_require_messageButtons sent with a template or mediaButtons only with a text message
404Template not found or not approvedCheck the name and language against your approved list (GET /whatsapp/templates)
402WhatsApp wallet balance too low for this categoryTop up; alert ops, don't retry in a loop
403 / 409WhatsApp not enabled, or no business account connectedFinish onboarding in the dashboard
401Invalid API keyFix configuration
502The send to WhatsApp failed upstreamLog message, which carries the reason. Don't auto-resend

Never resend after a timeout. If your request timed out, the message may already be on the customer's phone. Retry only when the connection was refused or DNS failed, which means the request never left your server. This is the same rule as our SMS guide.

6. Fall back to SMS

Not everyone is on WhatsApp, and some messages, such as OTPs, can't wait. Because WhatsApp and SMS sit on the same platform and API key, a fallback is a few lines:

PHP
<?php
function notify_shipped(string $phone, string $name, string $order, string $url): string
{
    $wa = api_post('/whatsapp/send', [
        'to' => $phone, 'template' => 'order_shipped', 'params' => [$name, $order, $url],
    ]);
    if (($wa['status'] ?? '') === 'success') {
        return 'whatsapp';
    }

    // Fall back only when WhatsApp clearly did not take the message.
    if (in_array($wa['code'] ?? '', ['invalid_recipient', 'template_required'], true)) {
        api_post('/sms/send', [
            'sender_id'       => 'AVNZON',
            'recipient'       => $phone,
            'message'         => "Your order {$order} has been shipped. Track it here: {$url} -AVNZON",
            'dlt_template_id' => '1107161234567890123',
        ]);
        return 'sms';
    }
    return 'failed';
}
// api_post(): the curl_init() call from section 2, wrapped to return the decoded JSON.

The SMS text must match a DLT-approved template, with variables tagged by type. Our Tamil Nadu WhatsApp page compares WhatsApp and SMS costs for common message types.

7. What each message costs (October 2026)

CategoryTypical useApprox. Meta rate, India
MarketingOffers, launches, re-engagement≈ ₹0.8631 per delivered message
UtilityOrder, payment and appointment updates≈ ₹0.115, now also inside an open window
AuthenticationOTPs≈ ₹0.115
ServiceFree-form replies in a customer-started chat1,000 free per number per month, then billed

Add 18% GST and the platform fee. Meta changed two things on 1 October 2026: service replies stopped being unlimited, and utility templates inside an open window started being billed again. Rates are revised periodically; the full breakdown with worked examples is in WhatsApp Business API pricing in India.

8. The same send on Meta's Cloud API

If you integrate with Meta directly, the template send is a POST to the Graph API with your phone number ID and a system-user access token. Keep the API version in one constant: Meta's current version is v26.0 (July 2026), and older versions are retired on a schedule.

PHPmeta-cloud-api.php
<?php
const GRAPH_VERSION = 'v26.0';

$phoneNumberId = getenv('WA_PHONE_NUMBER_ID');
$body = [
    'messaging_product' => 'whatsapp',
    'recipient_type'    => 'individual',
    'to'                => '919876543210',
    'type'              => 'template',
    'template'          => [
        'name'       => 'order_shipped',
        'language'   => ['code' => 'en'],
        'components' => [[
            'type'       => 'body',
            'parameters' => [                     // positional: same order as {{1}}, {{2}}, {{3}}
                ['type' => 'text', 'text' => 'Priya'],
                ['type' => 'text', 'text' => 'A1023'],
                ['type' => 'text', 'text' => 'https://avz.in/t/9KX2'],
            ],
            // Named-parameter templates add 'parameter_name' => 'first_name' to each entry.
        ]],
    ],
];

$ch = curl_init('https://graph.facebook.com/' . GRAPH_VERSION . "/{$phoneNumberId}/messages");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 20,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . getenv('WA_ACCESS_TOKEN'),
                               'Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($body),
]);
$res = json_decode((string) curl_exec($ch), true);
curl_close($ch);
// Success: $res['messages'][0]['id'] is the wamid. Errors: $res['error']['code'].

Meta error codes you'll meet most often:

CodeMeaning
131047More than 24 hours since the user replied; a template is required
132000Number of parameters doesn't match the template
132001Template doesn't exist in that language or isn't approved
131026Message undeliverable, for example not a WhatsApp number
131049Not delivered to protect ecosystem engagement (marketing frequency limits)
130429 / 131056Throughput limit reached / too many messages to one recipient
190Access token expired

Delivery and reply events arrive by webhook. Meta verifies your endpoint with a GET (note that PHP turns hub.mode into hub_mode) and signs every POST with your app secret, so check the signature before trusting the body:

PHPwebhook.php
<?php
// webhook.php
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $ok = ($_GET['hub_mode'] ?? '') === 'subscribe'
       && hash_equals((string) getenv('WA_VERIFY_TOKEN'), (string) ($_GET['hub_verify_token'] ?? ''));
    http_response_code($ok ? 200 : 403);
    header('Content-Type: text/plain');
    echo $ok ? (string) ($_GET['hub_challenge'] ?? '') : '';
    exit;
}

$raw       = file_get_contents('php://input');
$expected  = 'sha256=' . hash_hmac('sha256', $raw, (string) getenv('WA_APP_SECRET'));
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}

$event = json_decode($raw, true);
// Queue $event for processing and return 200 quickly; Meta retries slow or failed deliveries.
http_response_code(200);

Going direct means you also handle Business verification, number registration, template submission, token rotation and Meta billing yourself. Going through a provider trades a platform fee for one API covering WhatsApp and SMS, onboarding help and a single invoice. Either way, the template rules and prices above are Meta's.

9. Why not a WhatsApp Web script?

Search for "send WhatsApp message using PHP free" and you'll find scripts that drive WhatsApp Web, or wrappers around unofficial gateways. They work until the number gets banned, and for business sending that usually happens quickly. You lose the number, its chat history and every customer who saved it. The official API costs money per message, but it keeps the number, adds the business display name and lets you send at scale. For a business, that is the only option that lasts.

More guides: SMS in Laravel, Python, Node.js, Java, or the developer hub. Product details are on WhatsApp Business API and, for local onboarding, WhatsApp API in Coimbatore.

FAQ

Sending WhatsApp from PHP: FAQ

Can I send WhatsApp messages from PHP for free?
Not business messages at any volume. The official WhatsApp Business Platform charges per delivered message: in India about ₹0.8631 for marketing and about ₹0.115 for utility or authentication templates (October 2026, plus 18% GST and a platform fee). Replies inside a chat the customer started are free for the first 1,000 a month per business number, then billed.
Why can't I send a normal text message to a new customer?
WhatsApp only lets a business send free-form messages within 24 hours of the customer's last message. Outside that window, or to someone who has never messaged you, you must use a template Meta has approved. The AvanceZone API returns the error code template_required when this happens.
Are PHP scripts that automate WhatsApp Web safe to use?
No. Scripts that drive WhatsApp Web or the consumer app break WhatsApp's terms, and the number running them is routinely banned, often with no way to recover it. Use the official Business API, either through a provider or Meta's Cloud API directly.
Do I need to be a Meta Tech Provider to use the API?
No. You can use Meta's Cloud API directly or go through a provider. AvanceZone is a Meta Tech Provider, so we handle onboarding, number verification and template approval and give you one simpler API alongside SMS.
How do I send a PDF invoice on WhatsApp from PHP?
Use a utility template approved with a document header, and pass header_media_url (a public HTTPS link to the PDF), header_media_type set to document, and header_filename. The example on this page shows the full request.
What should my code do when WhatsApp fails?
Read the error code. template_required means you need a template instead of free text; invalid_recipient or an undeliverable number means the person may not be on WhatsApp, so fall back to SMS; a 402 means the wallet needs topping up. Do not blindly resend: a timeout may already have delivered the message.
OFFICIAL API, LOCAL HELP

Get WhatsApp API Access from a Meta Tech Provider

We connect your number, get your templates approved and give you one API key for WhatsApp and SMS.