Appearance
Set Up Webhooks
Webhooks let your application receive WhatsApp events from a ChatBreez instance in real time. Instead of polling the API for new messages, ChatBreez sends an HTTP POST request to your server whenever a matching event occurs.
This guide covers how to register a webhook from the dashboard and how to build an endpoint that listens for and processes those events.
How It Works
- You add a webhook URL on the instance detail page in the ChatBreez dashboard.
- A WhatsApp user sends a message to your connected instance.
- ChatBreez sends a
POSTrequest to your webhook URL with a JSON payload describing the event. - Your application reads the payload, handles the event, and responds with
200 OK.
WhatsApp user → ChatBreez instance → POST https://your-app.com/webhooks/chatbreez → Your applicationYour webhook URL must be publicly reachable over HTTPS. ChatBreez cannot deliver events to localhost unless you expose it with a tunnel (for example, ngrok or Cloudflare Tunnel).
Set Up From The Dashboard
Configure webhooks only from the dashboard. There is no public API for registering webhook URLs.
- Sign in to chatbreez.com.
- Open the dashboard.
- Go to your WhatsApp instances.
- Select the instance you want to configure.
- On the instance detail page, enter your webhook URL.
- Save the webhook.
After saving, ChatBreez starts sending incoming message events to that URL for the selected instance.
Listen To Webhook Events
To integrate ChatBreez with your application, create an HTTP endpoint that accepts POST requests, reads the JSON body, and acts on the event.
1. Create a webhook endpoint
Add a route in your application that listens for POST requests. Choose any path you control, as long as it matches the URL you saved in the dashboard.
Example endpoint:
http
POST https://example.com/webhooks/chatbreez2. Read the request body
Each webhook request has a JSON body with three top-level fields:
| Field | Type | Description |
|---|---|---|
event | string | The event type (for example, messages.upsert). |
instance | string | The ChatBreez instance name that received the message. |
data | object | Event-specific payload. For message events, this contains the message details. |
3. Handle the event
Use the event field to decide what your application should do. ChatBreez currently sends incoming message events for configured instances.
Supported event:
| Event | Description |
|---|---|
messages.upsert | A new message was received or updated on the instance. |
Inspect data for the message content, sender, and message ID. For text messages, the body is usually in data.message.conversation. Other message types (images, documents, audio) use different fields inside data.message.
Example payload:
json
{
"event": "messages.upsert",
"instance": "my-instance",
"data": {
"key": {
"remoteJid": "[email protected]",
"fromMe": false,
"id": "MESSAGE_ID"
},
"message": {
"conversation": "Hello"
}
}
}Field reference for messages.upsert:
| Field | Description |
|---|---|
data.key.remoteJid | Sender WhatsApp ID. The phone number is the part before @s.whatsapp.net. |
data.key.fromMe | true if the message was sent by your instance; false if it was received from a contact. |
data.key.id | Unique message ID. Use this for idempotency and deduplication. |
data.message.conversation | Plain-text message body, when the message is a text message. |
4. Respond quickly
Return a 2xx response as soon as your server accepts the event. ChatBreez treats non-success responses as delivery failures.
Do not run slow work (database writes, third-party API calls, AI processing) before responding. Acknowledge the webhook first, then process the event in the background.
json
{ "success": true }A 200 OK with any small JSON body is enough.
Integration Examples
These examples show a complete flow: receive the webhook, read the event, handle a new message, and return 200 OK.
javascript
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/chatbreez', (req, res) => {
const { event, instance, data } = req.body;
if (event === 'messages.upsert' || event === 'MESSAGES_UPSERT') {
const sender = data?.key?.remoteJid?.split('@')[0];
const text = data?.message?.conversation;
const messageId = data?.key?.id;
// Skip messages sent by your own instance
if (!data?.key?.fromMe && text) {
console.log(`New message on ${instance} from ${sender}: ${text}`);
// Queue background work here instead of awaiting it in the request
processMessage({ instance, sender, text, messageId });
}
}
res.status(200).json({ success: true });
});
function processMessage({ instance, sender, text, messageId }) {
// Save to database, trigger automation, reply via ChatBreez API, etc.
}
app.listen(3000, () => {
console.log('Listening for ChatBreez webhooks on port 3000');
});php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
Route::post('/webhooks/chatbreez', function (Request $request) {
$event = $request->input('event');
$instance = $request->input('instance');
$data = $request->input('data', []);
if (in_array($event, ['messages.upsert', 'MESSAGES_UPSERT'], true)) {
$fromMe = $data['key']['fromMe'] ?? false;
$text = $data['message']['conversation'] ?? null;
$sender = isset($data['key']['remoteJid'])
? explode('@', $data['key']['remoteJid'])[0]
: null;
if (!$fromMe && $text) {
Log::info('New WhatsApp message', [
'instance' => $instance,
'sender' => $sender,
'text' => $text,
'message_id' => $data['key']['id'] ?? null,
]);
// Dispatch a job for slower processing
// ProcessIncomingMessage::dispatch($instance, $sender, $text);
}
}
return response()->json(['success' => true]);
});python
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.post("/webhooks/chatbreez")
def chatbreez_webhook():
payload = request.get_json(silent=True) or {}
event = payload.get("event")
instance = payload.get("instance")
data = payload.get("data") or {}
if event in ("messages.upsert", "MESSAGES_UPSERT"):
key = data.get("key") or {}
message = data.get("message") or {}
if not key.get("fromMe") and message.get("conversation"):
sender = (key.get("remoteJid") or "").split("@")[0]
text = message["conversation"]
message_id = key.get("id")
print(f"New message on {instance} from {sender}: {text}")
# enqueue_background_job(instance, sender, text, message_id)
return jsonify({"success": True}), 200
if __name__ == "__main__":
app.run(port=3000)Test Locally
During development, your app usually runs on localhost, which ChatBreez cannot reach directly. Use a tunnel to expose your local server:
- Start your webhook receiver locally (for example, on port
3000). - Start a tunnel:
bash
ngrok http 3000- Copy the generated HTTPS URL (for example,
https://abc123.ngrok-free.app). - In the ChatBreez dashboard, set your webhook URL to:
http
POST https://abc123.ngrok-free.app/webhooks/chatbreez- Send a WhatsApp message to your connected instance and confirm your server logs the event.
Best Practices
- Respond fast. Return
200 OKwithin a few seconds. Move heavy processing to a queue or background job. - Make handlers idempotent. The same event may be delivered more than once. Store
data.key.idbefore processing and skip duplicates. - Ignore your own messages. Check
data.key.fromMeso you do not react to messages sent by your instance. - Use HTTPS in production. Webhook URLs must use
https://. - Keep logs. Log
event,instance, anddata.key.idto debug missed or duplicate deliveries.
Troubleshooting
| Issue | What to check |
|---|---|
| Webhook does not receive events | Confirm the instance is connected and the webhook URL is saved on the instance detail page. |
| Request fails when saving the webhook | Make sure the URL is provided and starts with https://. |
| Events arrive more than once | Make your handler idempotent by storing the message ID before processing. |
| Webhook times out | Return 200 OK quickly, then process long-running tasks in the background. |
| Local testing does not work | Use a tunnel such as ngrok and paste the public HTTPS URL into the dashboard. |
