PHP integration
Send WhatsApp messages with PHP
A complete, runnable PHP example for sending WhatsApp messages through the SPWP REST API. There is no SDK to install — PHP talks to the API over ordinary HTTP, so you can paste this straight into an existing project.
About 5 minutes to your first delivered message
1. Setup
Create an API key in the dashboard under Accounts → your account → API keys, then make it available to your PHP code as an environment variable. Never commit the key or ship it in client-side code.
# The cURL extension ships with virtually every PHP install.
php -m | grep curl2. Send a text message
One POST with a recipient and a body. The API replies 202 Accepted with a message id in QUEUED state; delivery happens asynchronously and you can track it by id or webhook.
<?php
const SPWP_API_BASE = "https://api.spwp.app/api/v1";
function spwp_send_text(string $to, string $text): array
{
$ch = curl_init(SPWP_API_BASE . "/public/messages/text");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("SPWP_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(["to" => $to, "text" => $text]),
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$body = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException("SPWP {$body['code']}: {$body['message']}");
}
return $body["data"];
}
$message = spwp_send_text("9647503505440", "Your verification code is 482310");
echo $message["id"], " ", $message["status"], PHP_EOL;3. Send an image, document or other media
Media endpoints take the same shape, with a source that is either a public HTTPS URL we fetch server-side or a base64 data URL you upload inline. Swap the path segment for video, audio, document or location.
<?php
function spwp_send_image(string $to, string $source, ?string $caption = null): array
{
$ch = curl_init(SPWP_API_BASE . "/public/messages/image");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("SPWP_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"to" => $to,
"source" => $source,
"caption" => $caption,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);
return $body["data"];
}4. Receive replies and delivery receipts
Inbound messages and status changes arrive as signed POSTs to your webhook URL. Recompute the HMAC over the timestamp and the raw request body — re-serialised JSON will not match — and reject anything older than five minutes.
<?php
// webhook.php — point your SPWP webhook URL here.
$ts = $_SERVER["HTTP_X_WA_TIMESTAMP"] ?? "";
$sig = $_SERVER["HTTP_X_WA_SIGNATURE"] ?? "";
preg_match("/v1=([a-f0-9]+)/", $sig, $m);
$v1 = $m[1] ?? "";
if (!$ts || !$v1) {
http_response_code(400);
exit;
}
// Replay protection: five-minute window.
if (abs(microtime(true) * 1000 - (int) $ts) > 5 * 60000) {
http_response_code(400);
exit;
}
$body = file_get_contents("php://input");
$expected = hash_hmac("sha256", "$ts.$body", getenv("SPWP_WEBHOOK_SECRET"));
if (!hash_equals($expected, $v1)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// …queue the work, respond immediately…
http_response_code(200);Error handling
Errors carry a non-2xx status and a stable code string you can branch on: VALIDATION_FAILED, INVALID_API_KEY, ACCOUNT_NOT_CONNECTED, RATE_LIMITED, MEDIA_FETCH_FAILED. Retry on 429 while honouring Retry-After; treat 401 as a configuration problem rather than a transient one.
Next steps
The full reference covers every endpoint, field and error code, plus the webhook payload shapes.