Dev

How to send a WhatsApp message by API (Node.js, Python and PHP)

Sending a WhatsApp message from code is an HTTP call. You point to your instance, pass your API key and secret, and send the number with country code and the text. Below are examples you can copy in four languages, plus the checks that save you headaches when sending moves from testing to production.

This article is also available in: Português · Español

What you need before sending

Four things: a ZapZap account, an instance (the WhatsApp number) connected by QR code in the dashboard, the API key and secret, and the instance ID. The key (x-api-key) and secret (x-api-secret) are in the dashboard under Settings and API, and the instance ID is shown in the instances list.

Every request carries the two authentication headers. There is no OAuth and no session: the authentication docs show the full format. The secret is for your server only. Never put it in code that runs in the browser or in the customer's app.

First test with cURL

Before writing code, confirm everything works with a direct call. Replace the uppercase values with yours. The number goes with country code and digits only (55 for Brazil, then area code and number).

cURL

curl -X POST https://api.zapzapapi.com/api/v1/SEU_INSTANCE_ID/send/text \
  -H "Content-Type: application/json" \
  -H "x-api-key: SUA_API_KEY" \
  -H "x-api-secret: SEU_API_SECRET" \
  -d '{"number":"5511999999999","text":"Olá! Teste de envio pela API."}'

Send a WhatsApp message with Node.js

On Node.js 18 or later, fetch is built in, so there is nothing to install. Keep the credentials in environment variables.

Node.js

const resposta = await fetch(
  `https://api.zapzapapi.com/api/v1/${process.env.ZAPZAP_INSTANCE_ID}/send/text`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.ZAPZAP_API_KEY,
      "x-api-secret": process.env.ZAPZAP_API_SECRET,
    },
    body: JSON.stringify({
      number: "5511999999999",
      text: "Olá! Seu pedido saiu para entrega.",
    }),
  },
);

if (!resposta.ok) {
  throw new Error(`Falha ${resposta.status}: ${await resposta.text()}`);
}
console.log(await resposta.json());

Send a WhatsApp message with Python

In Python, the requests library keeps it short. Always set a timeout so the call never hangs.

Python

import os
import requests

url = f"https://api.zapzapapi.com/api/v1/{os.environ['ZAPZAP_INSTANCE_ID']}/send/text"
cabecalhos = {
    "Content-Type": "application/json",
    "x-api-key": os.environ["ZAPZAP_API_KEY"],
    "x-api-secret": os.environ["ZAPZAP_API_SECRET"],
}
corpo = {"number": "5511999999999", "text": "Olá! Sua consulta está confirmada."}

resposta = requests.post(url, json=corpo, headers=cabecalhos, timeout=30)
resposta.raise_for_status()
print(resposta.json())

Send a WhatsApp message with PHP

In PHP, native cURL does the job with no extra library.

PHP

<?php
$url = "https://api.zapzapapi.com/api/v1/" . getenv("ZAPZAP_INSTANCE_ID") . "/send/text";

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        "Content-Type: application/json",
        "x-api-key: " . getenv("ZAPZAP_API_KEY"),
        "x-api-secret: " . getenv("ZAPZAP_API_SECRET"),
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "number" => "5511999999999",
        "text" => "Olá! Seu boleto vence amanhã.",
    ]),
]);

$resposta = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status >= 400) {
    throw new RuntimeException("Falha $status: $resposta");
}
echo $resposta;

Best practices before going to production

Validate the number before sending: country code plus area code plus number, digits only. A malformed number is the most common error and the easiest to avoid. Handle the response: a 4xx status is almost always a problem with the request, so repeating it unchanged does not help, while a network failure or a 5xx can be retried a few times with increasing waits.

Keep the secret on the server and run the call in a queue or background task, not inside the user's click, so a slow send never freezes your screen. The API responds synchronously, and every send shows the typing indicator before delivering, with a duration proportional to the text length, which makes sending feel more natural.

Respect the pace. Sending many identical messages in a row to people who do not expect them is the pattern that leads to reports the most. No technique removes the risk of being blocked, but warming up the number, varying the text and sending only to people who agreed to receive reduces the chance. The anti-block guide covers each point.

Next steps: receive replies and integrate

Sending is half of the flow. To receive customer replies, set up a webhook and handle the events in your system, as the webhook docs show. If you use no-code tools, see the integrations with n8n, Make and others, which do the same send without code.

Quick summary

  • Sending is a POST to /api/v1/{instanceId}/send/text with x-api-key and x-api-secret.
  • The minimum body has number (with country code, digits only) and text.
  • Keep the key and secret on the server, in environment variables.
  • Handle errors: do not repeat a 4xx unchanged, retry 5xx with increasing waits.
  • No method removes the risk of blocking: consent, warm-up and pacing help.

Frequently asked questions

How do I send a WhatsApp message by API in PHP, Python or Node.js?

Make a POST to https://api.zapzapapi.com/api/v1/{instanceId}/send/text with the x-api-key and x-api-secret headers and a JSON body with number (with country code) and text. The article has ready code for cURL, Node.js, Python and PHP.

Do I need OAuth or a token that expires?

No. API v1 only uses the key and secret sent in the headers of each request. Keep the secret on the server and never in the browser.

What format does the phone number use?

Digits only, with country code and area code. For Brazil, 55 plus the area code and number, for example 5511999999999.

Does the API charge per message sent?

At ZapZap the price is per active number per month, prepaid, with no per-message charge. See the [pricing page](/precos) for details.

Can I turn off the typing indicator?

Yes, by sending digitando as false in the request body when you need a near-instant reply. By default it stays on and makes sending feel more natural.

Start now

Create your account, connect a number in minutes and test the API with warm-up and anti-ban. R$15 per active number/month, prepaid, no lock-in.