Send WhatsApp Messages with PHP cURL: A Practical Guide

·7 min read
Send WhatsApp Messages with PHP cURL: A Practical Guide

You have a PHP application, and you need it to send a WhatsApp notification. Maybe it is an order confirmation for an e-commerce store in Mumbai, or a verification code for a portal in Karachi. You chose cURL because you do not want to bloat your codebase with heavy third-party SDKs. You want something native, fast, and supported by every basic hosting server.

Setting this up is not difficult, but Meta's official documentation is spread across dozens of pages. It is easy to get lost in the terminology. This guide gets straight to the point. We will write a clean, production-ready PHP cURL script to send your first message, explain why certain errors happen, and show you how to fix them.

What You Need Before Writing Any PHP Code

Do not write a single line of PHP until you have gathered four specific pieces of information from your Meta Developer Dashboard. Without these, your cURL requests will return 400 or 401 error codes every time.

  • The Phone Number ID: This is a 15-digit number assigned by Meta. It is not your actual phone number. It is a unique identifier for your WhatsApp integration. You will find this in your Meta Developer Console under WhatsApp > API Setup.
  • A Temporary or Permanent Access Token: Meta uses Bearer tokens for authentication. A temporary token expires after 24 hours. It is fine for a quick test today, but useless for production. For a live system, you must generate a permanent token using a System User in your Meta Business Suite.
  • A Verified Recipient Number: If your Meta app is still in development mode, you cannot send messages to just anyone. You must add your personal phone number to the "To" field whitelist in the developer console and verify it via a verification code.
  • A Pre-Approved Template Name: Meta does not allow you to send free-text messages to start a conversation. You must use a template that Meta has approved. For testing, Meta provides a default template named hello_world. We will use this in our script.

If you try to bypass these rules, your script will fail. For example, if you want to test your system with real users before completing your company registration, you should understand the limitations of using WhatsApp API without business verification first.

The PHP cURL Code

Since we are avoiding external libraries, we will use PHP's native cURL functions. We will wrap the request in a clean script that sets the headers, builds the JSON payload, executes the call, and handles the response.

Because standard code block tags are not always supported in every browser environment, we have structured this complete PHP script inside a clean table. Each row represents a logical block of your PHP file. You can assemble these rows into a single file named send_whatsapp.php.

Line BlockPHP Code Construction
1<?php
2$accessToken = 'YOUR_ACTUAL_ACCESS_TOKEN';
3$phoneNumberId = 'YOUR_PHONE_NUMBER_ID';
4$recipientNumber = '923001234567'; // Use country code, no plus sign, no leading zeros
5$url = 'https://graph.facebook.com/v20.0/' . $phoneNumberId . '/messages';
6$payload = [
7    'messaging_product' => 'whatsapp',
8    'to' => $recipientNumber,
9    'type' => 'template',
10    'template' => [
11        'name' => 'hello_world',
12        'language' => ['code' => 'en_US']
13    ]
14];
15$headers = [
16    'Authorization: Bearer ' . $accessToken,
17    'Content-Type: application/json'
18];
19$ch = curl_init();
20curl_setopt($ch, CURLOPT_URL, $url);
21curl_setopt($ch, CURLOPT_POST, true);
22curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
23curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
24curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
25$response = curl_exec($ch);
26$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
27if (curl_errno($ch)) {
28    echo 'cURL Error: ' . curl_error($ch);
29} else {
30    echo 'HTTP Status: ' . $httpCode . "\n";
31    echo 'Response: ' . $response;
32}
33curl_close($ch);

Save this file on your local server or production hosting. Replace the placeholder values in lines 2, 3, and 4 with your actual Meta credentials and verified phone number. Run the script from your terminal using php send_whatsapp.php or load it in your browser.

How to Confirm the Message Was Sent

When you run the script, do not just assume a blank screen or a simple "200" status code means the user has the message on their phone. You need to inspect the raw JSON response returned by Meta's servers.

If the request is successful, Meta will return an HTTP status code of 200 OK. The response body will look like this:

{"messaging_product": "whatsapp", "contacts": [{"input": "923001234567", "wa_id": "923001234567"}], "messages": [{"id": "wamid.HBgLOTIzMDAxMjM0NTY3FQIAERg0M0U0RjU2NzRDODkxOEFCAA=="}]}

Look closely at the messages array. The presence of the wamid (WhatsApp Message ID) is your proof that Meta accepted your payload and has queued the message for delivery. Keep this ID in your database if you plan to track delivery statuses later using webhooks.

What to Do When the cURL Request Fails

Things rarely work on the first try. If your script failed, you are likely looking at one of three common issues. Here is how to diagnose and fix them immediately.

1. cURL Error 60: SSL Certificate Problem

This is the most common issue for developers running XAMPP, WAMP, or Local WP on Windows machines in Pakistan or India. The error message looks like this: cURL error 60: SSL certificate problem: unable to get local issuer certificate.

This happens because your local PHP installation does not have an updated list of trusted Certificate Authorities (CAs) to verify Meta's SSL certificate.

Do not fix this by setting CURLOPT_SSL_VERIFYPEER => false. That is a terrible practice. It disables SSL verification entirely, leaving your production app vulnerable to man-in-the-middle attacks.

Instead, download the latest trusted CA bundle from the official curl website (cacert.pem). Save it in your PHP directory, open your php.ini file, find the line curl.cainfo, uncomment it, and set its value to the absolute path of your downloaded file. Restart your local server, and the error will disappear securely.

2. HTTP Status 400: Invalid Parameter

If you receive an HTTP 400 status code, Meta received your request but rejected the formatting. The response body will contain an error object with a specific message.

A common culprit is the phone number format. Meta's API is strict. If you format a Pakistani number as +92-300-1234567 or 03001234567, the API will reject it. It must be a clean string of numbers including the country code, without any leading zeros, spaces, or special characters. For example: 923001234567.

3. HTTP Status 401: OAuth Exception

This means your token is wrong, expired, or does not have the necessary permissions. If you are using a temporary token, go back to the developer console and check if your 24 hours are up. If you are using a permanent token, ensure that your System User has been granted the whatsapp_business_messaging permission inside your Meta Business Manager.

Moving From Test Scripts to Production

Once you see the hello_world message land on your phone, you are ready to build a real system. But running a single script manually is very different from managing transactional notifications at scale.

First, you must replace the default template with your own custom templates. Whether you want to send OTPs, shipping alerts, or automated invoices, you must draft these templates in the Meta Business Manager and wait for their automated system to approve them. If you are setting up transactional billing, you should learn how to automate WhatsApp invoices legally to ensure you stay compliant with local tax and commerce laws.

Second, you need to understand the financial implications. Meta does not charge you a flat monthly fee for the API. Instead, they charge per conversation. A conversation is a 24-hour window that starts the moment your message is delivered. The cost depends on the category of the message (Utility, Authentication, or Marketing) and the country code of the recipient. To avoid unexpected bills, familiarize yourself with the actual WhatsApp API pricing model before launching your campaigns.

If managing Meta's complex developer console, generating system tokens, handling template approvals, and dealing with infrastructure updates feels like too much work for your engineering team, you can use a gateway service. WA Link is one option that simplifies this by providing a clean, wrapper API over Meta's infrastructure. It eliminates the need to manage complex tokens yourself. However, keep in mind that WA Link is a routing tool; it does not bypass Meta's fundamental commerce policies or template approval rules. You still need to write clean message templates that comply with Meta's guidelines.

Frequently Asked Questions

Can I send a custom text message without using a template?

Only if the customer has sent you a message first within the last 24 hours. This is called a user-initiated session. During this 24-hour window, you can use PHP cURL to send free-text messages, images, or files without using pre-approved templates. Once that window closes, your script must use an approved template to initiate contact again.

Why does my PHP script work on my local computer but fail on my live server?