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.

ShapeMeaning
<CHANNEL_ID>your channel ID
<CHANNEL_TOKEN>your channel token: 43 characters of letters, digits, - and _, with no prefix
66XXXXXXXXXa phone number in full international form, digits only
https://example.com/wagateyour 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.

bash
curl https://api-wagate.socialground.co/health
json
{ "status": "ok" }

The address has no /v1 here. Every other route is under /v1.

3. Send a text

bash
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.

json
{
  "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.

bash
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"}]}'
json
{
  "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

bash
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"}'
json
{
  "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.