Quickstart
Send your first message and receive your first webhook.
You need a WAgate channel with a linked WhatsApp number. This takes about five minutes.
Every example uses these shapes. Replace them with your own values.
| Shape | Meaning |
|---|---|
<CHANNEL_ID> | your channel ID |
<CHANNEL_TOKEN> | your channel token: 43 characters of letters, digits, - and _, with no prefix |
66XXXXXXXXX | a phone number in full international form, digits only |
https://example.com/wagate | your webhook receiver |
1. Get your channel token
In the console, add a number and scan its QR code. Then create an API token for the channel and copy it. The console shows it once.
Your channel ID is the name on the channel's row in the console. It is a string of letters, digits, hyphens and underscores.
2. Check that the API answers
GET /health needs no token.
curl https://api-wagate.socialground.co/health{ "status": "ok" }The address has no /v1 here. Every other route is under /v1.
3. Send a text
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/messages/text" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"to": "66XXXXXXXXX", "text": "Hello from WAgate"}'to is the recipient's phone number in international form with the country code, or a WhatsApp JID.
{
"id": "3EB0C767D26A1D8E2A41",
"chat_jid": "[email protected]",
"timestamp": "2026-09-17T03:00:00Z",
"status": "server_ack"
}status: "server_ack" means WhatsApp's servers took the message. It does not mean the recipient's phone has it. Delivery and read receipts arrive later as message.status webhooks.
If this fails, read the code in the JSON body and look it up in Errors. The three you are most likely to see on a new channel are unauthorized (401, wrong token), not_linked (409, the number is not linked yet) and sandbox_allowance_exceeded (429, the free sandbox channel used an allowance).
4. Receive a webhook
Every new receiver needs a secret of at least 24 characters. Pick one and keep it on your server. WAgate signs every delivery with it, and you check that signature in step 6. A request that adds a receiver without a secret is refused with 400 invalid_body.
curl -X PUT "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/webhooks" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"receivers": [{"url": "https://example.com/wagate", "events": ["message", "message.status"], "secret": "REPLACE_WITH_24_PLUS_CHARACTERS"}]}'{
"receivers": [
{
"url": "https://example.com/wagate",
"events": ["message", "message.status"],
"media_policy": "reference",
"secret_set": true
}
]
}The response never repeats the secret. It only says whether one is set.
Your URL must be a public address on http or https. A URL that resolves to a loopback, private, link-local or cloud-metadata address is refused at delivery time.
5. Fire a test event
curl -X POST "https://api-wagate.socialground.co/v1/sessions/<CHANNEL_ID>/webhooks/test" \
-H "Authorization: Bearer <CHANNEL_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/wagate"}'{
"status": "delivered",
"detail": "the receiver accepted a signed test event; nothing was sent to WhatsApp"
}The url must match a receiver you already set. Any other URL answers 404 not_found. If your receiver does not accept the test event, the call answers 400.
6. Check the signature
Every delivery carries X-Wagate-Signature: the lowercase hex HMAC-SHA256 of the raw request body, keyed with your secret. Check it before you parse the JSON. Full code is in Webhooks.
Next
- Messages: texts with replies and mentions, polls, reactions, deletes, read receipts.
- Webhooks and Webhook events: what arrives, in what shape.
- Limits and Errors: what a 429 means, and which errors mean you should not blindly retry.