NewAds Insights — see what each Facebook ad costs and the WhatsApp leads it brings. Free on every plan. See Ads Insights →
Lead Pattern

Developers · PHP

Send a WhatsApp message with PHP

Working PHP code (built-in cURL extension, no packages) for the Lead Pattern WhatsApp API — send an approved template, reply with text inside the 24-hour window, and handle errors and rate limits.

Last updated: 6 November 2026

✨ Ask AI about this page:ChatGPT logoChatGPTClaude logoClaudePerplexity logoPerplexity

Quick answer

Create an API key in your Lead Pattern dashboard, then POST JSON with PHP's cURL functions to https://public.leadpattern.com/v2/whatsapp/message/template (approved template) or https://public.leadpattern.com/whatsapp/message/text (reply within 24 hours), with the key in the Authorization header — no "Bearer" prefix. HTTP 201 with type "success" means WhatsApp accepted the message.

API reference

Step by step

Send a WhatsApp message with PHP

1

Get an API key

In your Lead Pattern project dashboard, open API Keys and create a key. A key can call every public API endpoint your plan includes — keep it secret and on your server, never in browser or app code.

2

Set the Authorization header

Send the key as-is in the Authorization header on every request — no "Bearer" prefix — plus Content-Type: application/json. The base URL is https://public.leadpattern.com.

3

Send a template message

To start a conversation, or to message someone whose 24-hour window has closed, POST an approved template to /v2/whatsapp/message/template with named placeholders (or /whatsapp/message/template with positional ones).

4

Send a text message inside the 24-hour window

Once the customer has written to you in the last 24 hours, POST to /whatsapp/message/text with "to" and content.text. Outside the window this returns 403 — send a template instead.

5

Handle the response and errors

A successful send returns 201 with type "success" and a WhatsApp messageId. Errors come back with an HTTP status (400, 401, 402, 403, 404, 409, 422, 429, 500). On 429, wait for the Retry-After header before retrying. The PHP code below does all of this.

1–2 · API key, Authorization header and response helperphp
<?php
// PHP 8+ with the built-in cURL extension — no Composer packages needed.
const LP_BASE_URL = 'https://public.leadpattern.com';
$apiKey = getenv('LEADPATTERN_API_KEY'); // YOUR_API_KEY — keep it on the server

/** POST JSON to the Lead Pattern API and return [status, decoded body, Retry-After]. */
function lp_post(string $path, array $payload, string $apiKey): array
{
    $retryAfter = null;
    $ch = curl_init(LP_BASE_URL . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => [
            'Authorization: ' . $apiKey, // the raw key, NO "Bearer " prefix
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS     => json_encode($payload),
        CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$retryAfter) {
            if (stripos($line, 'Retry-After:') === 0) {
                $retryAfter = (int) trim(substr($line, 12));
            }
            return strlen($line);
        },
    ]);
    $raw = curl_exec($ch);
    if ($raw === false) {
        throw new RuntimeException('Network error: ' . curl_error($ch));
    }
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    return [$status, json_decode($raw, true), $retryAfter];
}

class LeadPatternException extends RuntimeException
{
    public function __construct(public int $status, public $body, public ?int $retryAfter = null)
    {
        parent::__construct('HTTP ' . $status . ': ' . json_encode($body));
    }
}

function lp_handle(array $res): array
{
    [$status, $body, $retryAfter] = $res;
    if ($status === 201) {
        return $body['data']; // ['messageId' => ...] or ['messages' => [...]]
    }
    // e.g. ['type' => 'error', 'code' => 403, 'message' => '...']
    throw new LeadPatternException($status, $body, $retryAfter);
}
3 · Send an approved template (v2, named placeholders)php
<?php
function send_template(string $to, string $name, string $language, array $placeholders, string $apiKey): array
{
    $payload = [
        'messages' => [[
            'to'      => $to, // E.164 with country code, e.g. "+91XXXXXXXXXX"
            'content' => [
                'templateName' => $name,
                'language'     => $language,
                'templateData' => ['body' => ['placeholders' => $placeholders]],
            ],
        ]],
    ];
    return lp_handle(lp_post('/v2/whatsapp/message/template', $payload, $apiKey));
}

$data = send_template('+91XXXXXXXXXX', 'order_confirmation', 'en',
    [['name' => 'Customer'], ['order_id' => 'ORD-1234']], $apiKey);
echo $data['messages'][0]['messageId'];
Success response (201) — templatejson
{
  "type": "success",
  "data": {
    "messages": [
      {
        "status": "SENT",
        "recipient": "91XXXXXXXXXX",
        "messageId": "wamid.abc123"
      }
    ]
  }
}
4 · Send a text message inside the 24-hour windowphp
<?php
// Free-form text — only works within 24 hours of the customer's last message.
function send_text(string $to, string $text, string $apiKey, bool $previewUrl = false): array
{
    $payload = ['to' => $to, 'content' => ['text' => $text, 'previewUrl' => $previewUrl]];
    return lp_handle(lp_post('/whatsapp/message/text', $payload, $apiKey));
}

echo send_text('+91XXXXXXXXXX', 'Hello! How can I help you?', $apiKey)['messageId'];
Success response (201) — textjson
{
  "type": "success",
  "data": {
    "messageId": "wamid.abc123",
    "status": "sent",
    "recipient": "91XXXXXXXXXX"
  }
}
5 · Handle errors and rate limitsphp
<?php
try {
    send_text('+91XXXXXXXXXX', 'Your order has shipped.', $apiKey);
} catch (LeadPatternException $e) {
    if ($e->status === 429) {
        sleep($e->retryAfter ?? 60); // wait, then retry once
        send_text('+91XXXXXXXXXX', 'Your order has shipped.', $apiKey);
    } elseif ($e->status === 403) {
        // 24-hour window closed (or IP / plan restriction) -> send a template instead
        send_template('+91XXXXXXXXXX', 'order_confirmation', 'en',
            [['name' => 'Customer'], ['order_id' => 'ORD-1234']], $apiKey);
    } else {
        throw $e;
    }
}
Error response example (403, window closed)json
{
  "type": "error",
  "code": 403,
  "message": "Cannot send a free-form message. The 24-hour customer service window has expired. You may only send template messages after 24 hours of the last user message."
}

HTTP status codes

What each response means

StatusMeaning (from the API docs)
201Created — the message was accepted by WhatsApp. The response has type "success" and data with the messageId.
400Malformed payload, invalid JSON or a missing required field (e.g. "to" is required). Also returned when the project has not finished WhatsApp onboarding.
401Missing, revoked, inactive or malformed API key.
402Workspace subscription inactive or blocked, or plan quota exceeded.
403IP not in the key's whitelist, plan does not include the feature, project suspended — or, for a free-form message, the 24-hour window has closed.
404The entity, template or recipient does not exist.
409Conflict — the resource already exists or is in the wrong state.
422Validation failed, e.g. the phone number is not valid or the WhatsApp account is unlinked.
429Rate limit exceeded. Wait for the Retry-After header, then retry.
500Unexpected server error.

Default limits per API key: 60 requests a minute and 10 requests in flight at once, plus 300 requests a minute per IP address. The effective limit is the lower of the key's limit and your plan's maximum.

Template v1 or v2?

  • v2 — /v2/whatsapp/message/template (recommended by the docs): body placeholders are named, an array of objects like {"name": "..."}. Supports carousel templates (content.templateData.cards) and an optional scheduledAt (a future ISO date-time) per message to schedule instead of sending now.
  • v1 — /whatsapp/message/template: body placeholders are positional — a flat array of values matched in order to the template's variables. Carousel is not supported on v1.
  • Both take a messages array, so one request can send to several recipients. AUTHENTICATION (OTP) templates use their own endpoint — see the API docs.
  • For a DOCUMENT header, pass templateData.header with type, mediaUrl and filename (e.g. "invoice.pdf") so WhatsApp shows a readable file name.

When you need a template

The WhatsApp 24-hour window, which Meta calls the customer service window, starts when a customer messages your business and resets with every new message from them. While it is open you can send normal replies without approval. Once 24 hours pass with no customer message, you can only send pre-approved templates. The 24-hour window explained →

Questions developers ask

Does this PHP code need Guzzle or Composer?+

No. It uses PHP's built-in cURL extension. Any HTTP client works, because the API is plain HTTPS + JSON.

How do I send a WhatsApp message with PHP?+

Create an API key in your Lead Pattern dashboard, then POST JSON to https://public.leadpattern.com with the key in the Authorization header (no Bearer prefix). Use /v2/whatsapp/message/template for an approved template, or /whatsapp/message/text for a reply inside the 24-hour window.

Why does my text message return 403?+

Most often the customer's 24-hour window has closed — free-form messages are only allowed within 24 hours of the customer's last message. Send an approved template instead. A 403 can also mean the key's IP whitelist blocks your server, or your plan does not include the feature.

What are the API rate limits?+

By default 60 requests a minute and 10 concurrent requests per API key, plus 300 requests a minute per IP address. Going over returns 429 with a Retry-After header. Starter includes 1,000 API calls a month; Growth includes 1,00,000 a month with unlimited API keys.

Which phone number format does the API expect?+

E.164 with the country code, for example +91XXXXXXXXXX for India.

Lead Pattern

Get your API key — free 14-day trial.

Set up the official WhatsApp Business API, create a key and send your first message today.

14-day free trial · No credit card · Free WhatsApp API setup

Ask AI about this page

Get a quick summary of this page from your favourite AI assistant — based only on leadpattern.com. Be one of the first to try it.