Skip to main content
GOWA POSTs a JSON document to your URL for every WhatsApp event it receives: messages, receipts, reactions, edits, deletions, typing indicators, group changes, labels, newsletters and calls. There is no webhook registration endpoint. Configure webhooks at startup, or per device through /devices/{device_id}/webhook.

How delivery works

Each target URL is delivered independently, with its own secret and retries.

Configure

Equivalent flags: --webhook, --webhook-secret, --webhook-events, --webhook-ignore-jids, --webhook-insecure-skip-verify.

Events

Event names in the allowlist are case-insensitive.

Payload

Every request has the same envelope:
Replies add replied_to_id and quoted_body. Receipts use ids (array) and receipt_type. Group events carry chat_id, type and jids.

Media

With WHATSAPP_AUTO_DOWNLOAD_MEDIA=true (default), media is saved under statics/media/ and the media field holds the path:
When a caption is present, the field becomes {"path": "...", "caption": "..."} and the caption is also in body. With auto-download off, it is {"url": "https://mmg.whatsapp.net/...", "caption": "..."}; fetch the file later with GET /message/{message_id}/download?phone=<chat>. The full catalogue of payloads (polls, locations, contacts, view-once, ads referrals and more) is in the upstream webhook payload reference.

Verify signatures

Each request carries X-Hub-Signature-256: sha256=<hex>, an HMAC-SHA256 of the raw request body keyed with the webhook secret. Compute it over the exact bytes received, before parsing JSON.

Delivery and retries

  • Each attempt times out after 10 seconds.
  • Any non-2xx response or network error is retried. GOWA makes up to 5 attempts, waiting 1 s, 2 s, 4 s and 8 s between them.
  • After the last failure the event is dropped and logged. There is no persistent queue or dead-letter store.
  • Several URLs are delivered independently.
So: respond 2xx quickly, process in the background, and make handlers idempotent; a retried event can arrive more than once.

Filter events

By type: WHATSAPP_WEBHOOK_EVENTS=message,message.ack forwards only those events. By chat or sender: WHATSAPP_WEBHOOK_IGNORE_JIDS matches chat_id, from, chat_lid and from_lid. Entries starting with @ are wildcards for a whole address space (@g.us, @s.whatsapp.net, @lid); anything else is an exact JID.
@s.whatsapp.net also matches group messages, because their from is a user JID. Use @g.us to mute groups. The two filters combine: an event is sent only if its type is allowed and its JID is not ignored.

Per-device webhooks

Give a slot its own webhook, secret and event allowlist:
The same fields are accepted by POST /devices. Read the current setting with GET /devices/{device_id}/webhook. Send "webhook_url": "" to clear it. By default a per-device URL replaces the global WHATSAPP_WEBHOOK for that slot. With WHATSAPP_WEBHOOK_DEVICE_MERGE_GLOBAL=true it is added to the global URLs instead: the device URL uses the device secret and allowlist, the global URLs keep the global ones, and a URL present in both receives the event once. Ignore-JID rules apply to both.

Testing locally

Expose a local receiver with a tunnel (for example ngrok http 8080) and set WHATSAPP_WEBHOOK to the tunnel URL. With --debug=true, GOWA logs each delivery attempt and failure.

Other ways to receive events

  • WebSocket /ws?device_id=<id>: pushes pairing and status events to the dashboard. It is not a substitute for webhooks.
  • Polling: GET /chats and GET /chat/{chat_jid}/messages read GOWA’s local chat storage. See Receiving messages.