TwaBot Webhooks
Receive real-time WhatsApp events in your CRM, app, or backend system.
TwaBot webhooks push real-time event data to your server whenever something happens — a customer sends a message, your agent replies, a broadcast completes, etc. This lets your CRM stay in sync with WhatsApp conversations without polling.
How It Works
When an event occurs (e.g., a customer sends you a WhatsApp message), TwaBot sends an HTTP POST request to the URL you configured, with a JSON payload containing the event details.
Your CRM Server TwaBot WhatsApp
| | |
| | <-- Customer Message -- |
| <-- POST (webhook event) -- | |
| | |
| -- 200 OK --> | |
Your server should respond with a 200 status code within 10 seconds. TwaBot does not wait for or process your response body — only the status code matters.
Setup Guide
Build an HTTP endpoint (e.g., https://yourcrm.com/webhooks/twabot) that accepts POST requests with JSON body.
Log in → Sidebar → Webhooks
Fill in: Name (e.g., "My CRM"), your server URL, select which events to receive, and optionally set a signing secret.
TwaBot will POST event data to your URL in real-time whenever a subscribed event occurs.
Testing Webhooks
Before using your production server, you can test webhook delivery using free tools:
Option 1: webhook.site (Recommended)
1. Go to https://webhook.site
2. Copy the unique URL it gives you
3. Paste that URL as your webhook endpoint in TwaBot
4. Send yourself a WhatsApp message
5. See the real-time payload on webhook.site
Option 2: ngrok (For local development)
If your CRM runs locally, use ngrok to expose it:
# Start your local server on port 3000 ngrok http 3000 # Use the ngrok URL as your webhook endpoint # e.g., https://abc123.ngrok.io/webhooks/twabot
Option 3: Postman (Mock server)
Create a mock server in Postman and use its URL as your webhook endpoint to inspect incoming payloads.
Webhook Events
TwaBot supports 5 event types. You can subscribe to one or more events per webhook.
| Event | Triggered When |
|---|---|
message.received | A customer sends you a WhatsApp message |
message.sent | An agent or system sends a reply to a customer |
conversation.created | A new customer starts a conversation (first message) |
broadcast.completed | A broadcast campaign finishes sending all messages |
ai.replied | The AI auto-reply sends a response to a customer |
message.received
Triggered when a customer sends a WhatsApp message to your business number.
{
"event": "message.received",
"timestamp": "2026-04-02T10:30:45.000Z",
"data": {
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEw...",
"from": "919876543210",
"from_name": "Rahul Kumar",
"type": "text",
"text": "Hi, I want to check my order status",
"conversation_id": 1234,
"business_id": 56
}
}
For media messages, the data object will include media_type, media_id, and mime_type instead of text.
message.sent
Triggered when an agent, system, or API sends a message to a customer.
{
"event": "message.sent",
"timestamp": "2026-04-02T10:31:12.000Z",
"data": {
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEw...",
"to": "919876543210",
"type": "text",
"text": "Hi Rahul! Your order ORD-12345 is out for delivery.",
"sent_by": "agent",
"conversation_id": 1234,
"business_id": 56
}
}
conversation.created
Triggered when a new customer messages you for the first time (no existing conversation).
{
"event": "conversation.created",
"timestamp": "2026-04-02T10:30:45.000Z",
"data": {
"conversation_id": 1234,
"customer_phone": "919876543210",
"customer_name": "Rahul Kumar",
"first_message": "Hi, I saw your ad on Instagram",
"business_id": 56
}
}
broadcast.completed
Triggered when a broadcast campaign finishes sending all messages.
{
"event": "broadcast.completed",
"timestamp": "2026-04-02T11:45:00.000Z",
"data": {
"broadcast_id": 89,
"template_name": "diwali_offer",
"total_recipients": 5000,
"sent": 4850,
"failed": 150,
"business_id": 56
}
}
ai.replied
Triggered when the AI auto-reply feature sends a response to a customer.
{
"event": "ai.replied",
"timestamp": "2026-04-02T10:30:48.000Z",
"data": {
"message_id": "wamid.HBgMOTE5ODc2NTQzMjEw...",
"to": "919876543210",
"ai_response": "Your order ORD-12345 is currently out for delivery and should arrive by 5 PM today.",
"conversation_id": 1234,
"business_id": 56
}
}
Signature Verification
If you set a secret when creating your webhook, TwaBot signs every payload using HMAC-SHA256. This lets you verify that the request is genuinely from TwaBot and hasn't been tampered with.
The signature is sent in the X-TwaBot-Signature header.
How to verify (Node.js example)
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return signature === expected;
}
// In your Express route handler:
app.post('/webhooks/twabot', (req, res) => {
const signature = req.headers['x-twabot-signature'];
const isValid = verifySignature(req.body, signature, 'your_webhook_secret');
if (!isValid) {
console.error('Invalid webhook signature!');
return res.status(401).send('Unauthorized');
}
// Process the event
const { event, data } = req.body;
console.log('Received event:', event, data);
res.status(200).send('OK');
});
How to verify (Python example)
import hmac
import hashlib
import json
def verify_signature(payload, signature, secret):
expected = hmac.new(
secret.encode('utf-8'),
json.dumps(payload).encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# In your Flask/FastAPI handler:
@app.route('/webhooks/twabot', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-TwaBot-Signature', '')
if not verify_signature(request.json, signature, 'your_webhook_secret'):
return 'Unauthorized', 401
event = request.json['event']
data = request.json['data']
print(f'Received event: {event}', data)
return 'OK', 200
How to verify (PHP example)
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_TWABOT_SIGNATURE'] ?? '';
$secret = 'your_webhook_secret';
$expected = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
echo 'Unauthorized';
exit;
}
$data = json_decode($payload, true);
$event = $data['event'];
// Process the event
error_log("Received event: " . $event);
http_response_code(200);
echo 'OK';
?>
Retries & Failures
TwaBot tracks delivery failures for each webhook:
| Behavior | Details |
|---|---|
| Timeout | 10 seconds — your server must respond within this window |
| Failure tracking | Each failed delivery increments fail_count |
| Success reset | A successful delivery resets fail_count to 0 |
| Delivery mode | Async (non-blocking) — TwaBot does not retry failed deliveries automatically |
fail_count in your TwaBot dashboard.
POST /api/client/webhooks
Create a new webhook. Requires login session (dashboard authentication).
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Required | Webhook name (e.g., "My CRM") |
url | string | Required | Your server URL (must be HTTPS in production) |
events | array | Required | Array of event names to subscribe to |
secret | string | Optional | Signing secret for HMAC-SHA256 verification |
Example Request
curl -X POST https://twabot.com/api/client/webhooks \
-H "Content-Type: application/json" \
-H "Cookie: token=your_session_token" \
-d '{
"name": "My CRM Webhook",
"url": "https://yourcrm.com/webhooks/twabot",
"events": ["message.received", "conversation.created"],
"secret": "my_secret_key_123"
}'
{
"id": 7,
"message": "Webhook created."
}
GET /api/client/webhooks
List all webhooks for your business.
{
"webhooks": [
{
"id": 7,
"name": "My CRM Webhook",
"url": "https://yourcrm.com/webhooks/twabot",
"events": ["message.received", "conversation.created"],
"is_active": 1,
"last_triggered_at": "2026-04-02T10:30:45.000Z",
"fail_count": 0,
"created_at": "2026-04-01T09:00:00.000Z"
}
]
}
PUT /api/client/webhooks/:id
Update an existing webhook. All fields are optional — only the fields you include will be updated.
Request Body (all optional)
| Parameter | Type | Description |
|---|---|---|
name | string | New webhook name |
url | string | New server URL |
events | array | New event subscriptions |
secret | string | New signing secret (or null to remove) |
is_active | boolean | true to enable, false to disable |
{
"message": "Webhook updated."
}
DELETE /api/client/webhooks/:id
Permanently delete a webhook.
{
"message": "Webhook deleted."
}
Best Practices
1. Respond quickly
Your endpoint should return 200 OK as fast as possible (ideally under 2 seconds). If you need to do heavy processing, acknowledge the webhook first, then process asynchronously in the background.
2. Use a signing secret
Always set a signing secret and verify the X-TwaBot-Signature header. This prevents unauthorized parties from sending fake events to your endpoint.
3. Handle duplicates
In rare cases, an event might be delivered more than once. Use the message_id or conversation_id to deduplicate on your side.
4. Use HTTPS
Always use HTTPS for your webhook URL to ensure event data is encrypted in transit.
5. Monitor fail_count
Check your webhook's fail_count in the TwaBot dashboard regularly. A high count means your server is having issues receiving events.
6. Log incoming events
Log all incoming webhook events on your side for debugging. This helps when troubleshooting missing or incorrect data.
Need help? Contact us at support@twabot.com
Also see: REST API Documentation →