Send your first WhatsApp message with the API
From nothing to a delivered message and a webhook carrying the reply. Copy-paste curl throughout, with the errors you will actually hit and what each one means.
From nothing to a delivered WhatsApp message, then a webhook carrying the reply. Curl throughout, so it works the same whatever you end up writing it in.
You need a WhatsApp account on a phone you can scan a QR code with. Use a second number rather than your personal one if you have the choice — not because anything here is risky, but because a number you experiment with should not be the number your family uses.
Connect a number
In the console, create a session and scan the QR code with WhatsApp → Linked devices. This is the same mechanism as WhatsApp Web: your account stays on your phone, and the session is another linked device.
Wait for the status to reach connected before going further. A session that is still pairing will accept API calls and deliver nothing, which is a confusing first ten minutes.
Get an API key
Console → API keys → create one. You see the value once. It looks like ws_live_…, and it carries the same authority as your login, so it belongs in an environment variable and never in a repository.
export WAVESENDA_URL="https://api.wavesenda.com/api/v1"
export WAVESENDA_API_KEY="ws_live_..."Find your session id
curl -s "$WAVESENDA_URL/sessions/" \
-H "Authorization: Bearer $WAVESENDA_API_KEY"Take the id of the connected session. Everything you send is sent from a session, so most calls need it.
export SESSION="00000000-0000-0000-0000-000000000000"Send a message to yourself
Your own second number is the right first recipient: if it does not arrive, you know immediately, and you have not bothered anybody.
curl -s -X POST "$WAVESENDA_URL/messages/send/" \
-H "Authorization: Bearer $WAVESENDA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session": "'"$SESSION"'",
"to": "+237670000000",
"type": "text",
"text": "First message from the API."
}'The number goes in international format. The response carries a message id and a status — and that status is worth understanding before you build anything on it.
Understand what came back
| Status | Means | Does not mean |
|---|---|---|
| queued | We have it, WhatsApp does not yet | Anything about delivery |
| sent | WhatsApp accepted it | That a phone received it |
| delivered | It reached a device | That anybody read it |
| read | The chat was opened | — |
| failed | It will not be delivered | — |
sent is not delivered. A message to a number with no WhatsApp account is accepted and goes nowhere. If you are building anything that depends on a person receiving something, wait for delivered — this is the single most common mistake made in the first week, and we wrote a whole post about it.
Check a number before you trust it
Plenty of valid phone numbers have no WhatsApp account. Checking costs nothing and removes a whole class of mystery.
curl -s -X POST "$WAVESENDA_URL/messages/check/" \
-H "Authorization: Bearer $WAVESENDA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"session": "'"$SESSION"'", "numbers": ["+237670000000"]}'Do this over a list before a campaign rather than afterwards, when you are trying to explain a poor response rate.
Receive the reply
Sending is half of it. Register a webhook and inbound messages arrive at your endpoint as they happen.
curl -s -X POST "$WAVESENDA_URL/webhooks/" \
-H "Authorization: Bearer $WAVESENDA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/wavesenda",
"events": ["message.received", "message.status"]
}'Two rules for the endpoint, both learned the hard way by everybody:
- Answer 200 quickly and do the work afterwards. A handler that takes eight seconds will be retried, and you will process the same message twice.
- Verify the signature before trusting the body. The URL is guessable; the signature is not.
Use a request-inspection service while developing so you can see the payload shape before you write any parsing.
Send something that is not text
# 1. upload
curl -s -X POST "$WAVESENDA_URL/messages/media/" \
-H "Authorization: Bearer $WAVESENDA_API_KEY" \
-F "file=@invoice.pdf"
# 2. send it, using the id that came back
curl -s -X POST "$WAVESENDA_URL/messages/send/" \
-H "Authorization: Bearer $WAVESENDA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session": "'"$SESSION"'",
"to": "+237670000000",
"type": "document",
"media": "MEDIA_ID",
"caption": "Invoice #1847"
}'Errors you will actually hit
| What you see | What it means |
|---|---|
| 401 | Key missing, wrong, or revoked. Check the Bearer prefix |
| session_not_connected | The session is pairing or has dropped. Re-scan |
| Accepted, never delivered | Usually no WhatsApp account on that number |
| sending_paused | Deliberate. The number is being protected — check its health |
| 429 | You are going too fast. Back off rather than retrying in a loop |
Before you send to real people
- Only message people who contacted you first. This is the whole ballgame.
- Pace it. A new number sending hundreds of messages looks like what it looks like.
- Watch the delivered-versus-accepted gap as an early warning.
- Read what gets numbers banned before your first campaign, not after.
Next: build an agent to answer some of the replies, or the full quickstart for SDKs.
Common questions
Do I need a Meta developer account or business verification?
Not for this route. You are connecting an existing WhatsApp account as a linked device, the same way WhatsApp Web does, so there is no application process. The official Cloud API is a different product with different requirements.
Can I use my personal WhatsApp number?
Technically yes. Use a second number instead — a number you are testing automation against should not be the one your family messages, and the separation costs almost nothing.
Why did my message say sent but never arrive?
Most often the number has no WhatsApp account. Check it explicitly. Other causes are a block by the recipient, a phone that is off, or a number whose WhatsApp address differs from the number as typed, which happens in markets that have renumbered.
How fast can I send?
Slower than you want to. There is no single published number because it depends on the account’s age, history and reply rate. A new number should send a handful of messages a day and build up; anything that lets you send hundreds immediately is not protecting you.
What happens if my session disconnects?
Sending fails with a clear error rather than queueing silently, and the console shows the session as disconnected. Re-scanning the QR restores it. Phones that are off for long periods, or a logout from Linked Devices, are the usual causes.