WA Gateway

Webhook

Set your Webhook, read the events we send, check their signature, and handle retries.

We send your events by POST to one URL on your server: your Webhook. You can set it in the Dashboard (see Webhook) or with the API.

Set the Webhook

PUT /v1/webhooks
Body fieldRequiredMeaning
urlyesYour http or https address that receives events.
secretyesYour signing secret: a long random text.

Setting it again replaces the old one.

curl -X PUT https://api.wa.goupdev.net/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-site.com/whatsapp/events", "secret": "a-long-random-secret"}'

Answer 200: the Webhook, with id, url, secret and active.

ErrorWhy
400 {"error": "url and secret are required"}url or secret is missing.

Read the Webhook

GET /v1/webhooks

Answer 200: { "id", "url", "secret", "active" }, or null when no Webhook is set.

Events

Every event is a JSON object with an event field that names it.

message.received

Someone sent a Message to one of your Devices.

{
  "event": "message.received",
  "deviceId": 7,
  "from": "9665XXXXXXXX@s.whatsapp.net",
  "text": "Hello, is my order ready?",
  "providerMessageId": "3EB0C767D26A1D8B4F21"
}
FieldMeaning
deviceIdThe Device that received it.
fromThe sender's WhatsApp address. The digits before @ are their number.
textThe text, or null for Messages without text (images, audio, stickers...).
providerMessageIdWhatsApp's id for this Message. Use it to ignore an event you already handled.

message.status_changed

A Message you sent was delivered or read.

{
  "event": "message.status_changed",
  "deviceId": 7,
  "messageId": 1042,
  "status": "delivered"
}

messageId is the id you got when you sent the Message. status is delivered or read.

device.status_changed

A Device was logged out of WhatsApp. It needs a new QR scan before it can send again.

{
  "event": "device.status_changed",
  "deviceId": 7,
  "status": "logged_out"
}

Check the signature

Every event has an X-Signature header: the HMAC-SHA256 of the raw request body, using your signing secret, written in hex. Compute the same value on your side and compare. If they differ, ignore the request: it didn't come from us.

Use the raw body exactly as received, before parsing the JSON.

import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post('/whatsapp/events', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(req.body).digest('hex');
  const received = req.get('X-Signature') ?? '';
  const valid =
    received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  // Handle the event (quickly), then answer 200.
  res.sendStatus(200);
});

Retries

  • Answer with any 2xx status to confirm you received the event.
  • If your server answers anything else, or can't be reached, we try again: up to 5 attempts in total, waiting longer each time (about 10 seconds, then 20, 40 and 80).
  • After the last attempt, that event is dropped.

So:

  • Answer fast. Save the event and answer 200 at once. Do slow work afterwards.
  • Expect duplicates. A retry can deliver an event you already handled. Use providerMessageId, or messageId with status, to skip it.
  • Don't rely on order. Events can arrive in a different order from the one they happened in.

On this page