Skip to content
All articles
Developers

How to Send WhatsApp Messages with an API: Node.js, PHP and cURL

Send your first WhatsApp message from code in a few minutes, receive replies with webhooks, and test everything on a simulated device before you link a real number.

ChatKomodo Team3 min read
Table of contents
  1. How it works
  2. 1. Try it without a phone
  3. 2. Send a message
  4. 3. Receive replies with a webhook
  5. 4. Things to get right before going live
  6. Next steps

Order confirmations, one-time codes, appointment reminders, delivery updates: if your app already sends email or SMS, WhatsApp is usually where customers actually read the message. This guide shows the shortest path from an API key to a delivered WhatsApp message, and how to receive replies.

How it works

ChatKomodo links your own WhatsApp number as a device. You scan a QR code once, exactly like WhatsApp Web, and the number keeps working on the phone. Your app then calls a REST API, and ChatKomodo sends from that number:

  • POST /v1/sessions/{device}/messages sends a message: text, image, document, location, poll or contact card;
  • webhooks tell your server about replies and delivery problems as they happen.

1. Try it without a phone

You can start before linking a real number. A test device behaves like a linked number, but nothing reaches WhatsApp: messages are logged, your webhooks fire, and statuses move from sent to delivered to read.

curl -X POST https://api.chatkomodo.com/v1/sessions \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sandbox": true}'

The answer contains the device id. Details are in the test device guide.

2. Send a message

With cURL:

curl -X POST "https://api.chatkomodo.com/v1/sessions/$DEVICE_ID/messages" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{"chat_id": "+447700900123", "type": "text", "body": "Your order #1042 has shipped"}'

With the Node.js library:

import { ChatKomodo } from 'chatkomodo';

const ck = new ChatKomodo({ apiKey: process.env.CHATKOMODO_API_KEY });
await ck.messages.send(deviceId, {
  chat_id: '+447700900123',
  type: 'text',
  body: 'Your order #1042 has shipped',
}, { idempotencyKey: 'order-1042-shipped' });

With PHP or Laravel:

use ChatKomodo\Laravel\Facades\ChatKomodo;

ChatKomodo::sendText('+447700900123', 'Your order #1042 has shipped', 'order-1042-shipped');

The idempotency key matters more than it looks. If your request times out and you retry, the same key returns the first result instead of sending the message twice. Installing the libraries is covered in Libraries.

3. Receive replies with a webhook

Register an HTTPS endpoint and choose the events you want:

curl -X POST https://api.chatkomodo.com/v1/webhooks \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/webhooks/chatkomodo", "secret": "a-long-random-secret-of-32-characters-or-more", "events": ["message.received", "message.failed"]}'

Every delivery is signed with your secret in the X-Webhook-Signature header. Verify it on the raw body before trusting the event. The libraries do it in one line (parseWebhook in Node.js, Webhook::verify or the chatkomodo.webhook middleware in PHP/Laravel). With a test device you can play the customer yourself: POST /v1/sessions/{device}/sandbox/inbound makes a "customer" write to you, and your webhook receives it.

4. Things to get right before going live

  • Format numbers internationally: +44…, +62…, +1…. Local numbers work for your business's own country.
  • Respect the pace: messages are sent with human-like pauses, and a newly linked number sends more slowly for its first day. Bulk sends belong in a broadcast, not a tight loop.
  • Handle 429: when you exceed your key's request limit, the API answers 429 with Retry-After. The libraries wait and retry automatically.
  • Opt-outs: recipients who reply STOP are blocked from further messages. That is on purpose.

Next steps