Guide to Green API Webhooks: Features and Best Practices
Green API connects a WhatsApp account to an HTTP API. Each connected account is an instance, and the instance reports what happens on it: incoming messages, messages sent from the phone or the API, delivery and read statuses, calls, and changes to the instance's own state. Green API can deliver those notifications by calling your server, which it calls Webhook Endpoint technology.
This guide covers how to configure an instance's webhook URL and token, how Green API builds the Authorization header, the webhook types and payloads, how delivery and retries work, and the best practices for running a Green API receiver in production.
What are Green API webhooks?
Green API offers two ways to receive notifications. With the HTTP API, you poll with ReceiveNotification and acknowledge with DeleteNotification. With Webhook Endpoint technology, Green API calls your URL as soon as a notification is ready. Green API describes the second as the fastest, with capacity "limited only by the rate of processing webhooks on your server side".
Each instance has one webhookUrl, an optional webhookUrlToken, and a set of switches that decide which webhook types it sends. There's no signature. If you set a token, Green API sends it in the Authorization header of every request, and your server checks it.
Green API webhook features
| Feature | Details |
|---|---|
| Configuration | The SetSettings method, or the instance's settings in the console |
| URL | One webhookUrl per instance |
| Subscription model | Per-type switches such as incomingWebhook, outgoingWebhook and stateWebhook |
| Method and format | POST, Content-Type: application/json; charset=utf-8 |
| Event identifier | typeWebhook in the body |
| Signature | None |
| Authentication | webhookUrlToken, sent in the Authorization header |
| Retries | Every minute until delivered |
| Delivery guarantee | Within 24 hours |
| Throughput | About 50 notifications a second |
| Source IPs | 13 published addresses |
| Alternative | Polling with ReceiveNotification and DeleteNotification |
Common events
The body's typeWebhook field names the webhook type:
typeWebhook | Sent when |
|---|---|
incomingMessageReceived | A message or file arrives |
outgoingMessageReceived | A message is sent from the phone |
outgoingAPIMessageReceived | A message is sent through the API |
outgoingMessageStatus | An outgoing message's status changes |
incomingMessageStatus | An incoming message's status changes |
stateInstanceChanged | The instance's authorisation state changes |
incomingCall | A call comes in |
outgoingCall | A call is made |
quotaExceeded | Developer plan limits are exceeded |
statusInstanceChanged | The instance's socket state changes (archived) |
deviceInfo | The phone's device and battery level are reported (archived) |
incomingBlock | A chat is added to the blocked contacts list (archived) |
Every body starts with the same envelope: typeWebhook, instanceData (idInstance, wid and typeInstance) and timestamp in Unix seconds. An incoming text message:
{
"typeWebhook": "incomingMessageReceived",
"instanceData": {
"idInstance": 7103000000,
"wid": "79876543210@c.us",
"typeInstance": "whatsapp"
},
"timestamp": 1588091580,
"idMessage": "F7AEC1B7086ECDC7E6E45923F5EDB825",
"senderData": {
"chatId": "79001234567@c.us",
"sender": "79001234567@c.us",
"chatName": "John",
"senderName": "John",
"senderContactName": "John Doe"
},
"messageData": {
"typeMessage": "textMessage",
"textMessageData": {
"textMessage": "I use Green-API to send this message to you!"
}
}
}
messageData.typeMessage says what kind of message it is, and the matching object holds the content. Green API documents separate formats for text, quoted, media, location, contact, poll, reaction, edited and deleted messages, and more.
A status update for a message you sent:
{
"typeWebhook": "outgoingMessageStatus",
"chatId": "79001234567@c.us",
"instanceData": {
"idInstance": 7103000000,
"wid": "79876543210@c.us",
"typeInstance": "whatsapp"
},
"timestamp": 1727691478,
"idMessage": "3EB0608D6A2901063D63",
"status": "sent",
"sendByApi": true
}
status can be sent, delivered, read, failed, noAccount, notInGroup or suspended, and the deprecated yellowCard, which suspended replaces. Green API says failed and noAccount can't be switched off in SetSettings, so your handler must process them.
Setting up Green API webhooks
Call SetSettings for the instance with your URL, an optional token and the types you want:
{
"webhookUrl": "https://example.com/webhooks/green-api",
"webhookUrlToken": "Bearer 3f6b0c2e9a7d4c1b8e5f2a6d9c0b3e7f",
"incomingWebhook": "yes",
"outgoingWebhook": "yes",
"outgoingMessageWebhook": "yes",
"stateWebhook": "yes",
"deviceWebhook": "no"
}
Or open the instance in the console. Once the instance is authorised, its webhook settings show the URL, a switch per webhook type and the Webhook URL Token. If the settings don't appear, the instance isn't authorised yet.
Securing Green API webhooks
Set webhookUrlToken to a long random value. Green API's rules for the header it sends are:
- If you set
Basic <token>, the header isAuthorization: Basic <token>. - If you set
Bearer <token>, the header isAuthorization: Bearer <token>. - If you set the token without a type, the header defaults to
Bearer <token>.
Setting the type explicitly removes any doubt. Compare the whole header value in constant time:
const crypto = require('crypto');
const express = require('express');
function verifyHeader(value, expected) {
if (typeof value !== 'string' || !expected) return false;
const received = Buffer.from(value, 'utf8');
const wanted = Buffer.from(expected, 'utf8');
return received.length === wanted.length && crypto.timingSafeEqual(received, wanted);
}
const app = express();
app.post('/webhooks/green-api', express.raw({ type: '*/*' }), (req, res) => {
const token = process.env.GREEN_API_WEBHOOK_TOKEN; // the value after "Bearer "
if (!token) return res.sendStatus(500); // fail closed
if (!verifyHeader(req.headers['authorization'], `Bearer ${token}`)) {
return res.sendStatus(401);
}
const notification = JSON.parse(req.body.toString('utf8'));
// Route on notification.typeWebhook, then process asynchronously.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
def verify_green_api_header(value) -> bool:
token = os.environ.get("GREEN_API_WEBHOOK_TOKEN")
if not token or not value:
return False
return hmac.compare_digest(value.encode(), f"Bearer {token}".encode())
You can also allow only Green API's published source addresses in your firewall: 46.101.109.139, 51.250.12.167, 51.250.84.44, 51.250.95.149, 89.169.137.216, 158.160.49.84, 165.22.93.202, 167.172.162.71, 104.248.252.93, 158.160.139.176, 64.226.111.11, 207.154.255.195 and 94.131.81.171.
Make Green API webhooks production-ready. Hookdeck Event Gateway checks the
Authorizationheader on every Green API notification, queues it the moment it arrives, and delivers it to your service at a pace it can handle.
Green API webhook limitations and pain points
A slow server delays every notification
The Problem: Messages reach your application minutes late during busy periods.
Why It Happens: Green API retries failed deliveries at a "1-minute interval", and sends about 50 notifications a second. If your server is down or slow, notifications queue behind each other, and each failure costs at least a minute.
Workarounds:
- Return
200as soon as the notification is stored, and process it afterwards. - Keep the receiving server separate from slow work such as AI replies or CRM writes.
- Use Green API's queue methods to see how many webhooks are waiting.
How Hookdeck Can Help: Hookdeck accepts each notification immediately, so Green API's queue keeps moving, and then delivers to your service with its own rate limits and retries.
Delivery is guaranteed for 24 hours only
The Problem: After a long outage, some notifications never arrive.
Why It Happens: Green API says "the delivery of incoming webhooks is guaranteed within 24 hours". Beyond that, there's no promise.
Workarounds:
- Monitor your endpoint and fix outages quickly.
- After an outage, check the incoming and outgoing message journals for anything you missed.
How Hookdeck Can Help: Hookdeck stores every notification it receives, so your service can be down for longer than a day and still replay everything once it recovers.
The token is static and unsigned
The Problem: Anyone who learns your URL and token can send fake messages to your bot.
Why It Happens: Green API authenticates with a fixed Authorization header and doesn't sign the body.
Workarounds:
- Use a long random token, and change it with
SetSettingsif it might have leaked. - Restrict the endpoint to Green API's published IP addresses.
- Serve the endpoint over HTTPS only.
How Hookdeck Can Help: Hookdeck's Green API source type compares the whole Authorization header value. Enter it exactly as Green API sends it, which is your webhookUrlToken as you set it, with Bearer in front if you set it without a type. Verification is optional on the source, so leave it off if you didn't set a token.
Statuses and duplicates
The Problem: You count the same message twice, or a late delivered status overwrites read.
Why It Happens: If you process a notification but Green API doesn't see your success response, it sends it again a minute later. Each status change for a message is also its own webhook, and retries can change the order they reach you.
Workarounds:
- Deduplicate on
typeWebhook,idMessageand, for statuses,status. - Treat
readas later thandelivered, anddeliveredas later thansent, whatever order they arrive in.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated notifications based on body fields such as idMessage and status before they reach your service.
Best practices
Compare the whole header in constant time
Check the full Authorization value, scheme included, with crypto.timingSafeEqual (after a length check) or hmac.compare_digest. Fail closed when the token isn't configured.
Enable only the types you use
Every switch you turn on adds traffic. Leave deviceWebhook and other types you don't handle off, but always handle failed and noAccount statuses, which can't be turned off.
Route on typeWebhook, then typeMessage
Branch on typeWebhook first and on messageData.typeMessage second. Log and skip types you don't recognise rather than failing the request.
Use instanceData.idInstance when you run several instances
If several WhatsApp numbers share one endpoint, idInstance tells you which one a notification belongs to. Give each instance its own token if you want to tell them apart securely.
Acknowledge first, process after
Store the notification, return 200, and reply to messages from a worker. See why to process webhooks asynchronously and our guide to webhook idempotency.
Conclusion
Green API sends each WhatsApp notification for an instance to its webhookUrl, identified by typeWebhook and authenticated by the webhookUrlToken in the Authorization header rather than a signature. Set the token with an explicit type, compare the whole header in constant time, answer quickly so the one-minute retries don't build a backlog, and deduplicate on idMessage.
Hookdeck Event Gateway checks each Green API notification, stores it, and retries into your service on your schedule.
Get started with Hookdeck for free and handle Green API webhooks reliably in minutes.