Webhooks
Webhooks send real-time HTTP POST requests to your server when events occur in EmacronAI CRM — OTP verified, message delivered, reply received, broadcast completed.
X-EmacronAI CRM-Signature header to confirm events are from EmacronAI CRM and not a third party.Setup
- 1
Create an endpoint
Add a POST endpoint to your server that accepts JSON. It must respond with HTTP 200 within 10 seconds.
- 2
Register in dashboard
Go to Settings → Webhooks → Add Endpoint. Paste your URL and select the events you want to receive.
- 3
Copy your signing secret
EmacronAI CRM generates a signing secret per endpoint. Save it as an environment variable — never commit it to source code.
- 4
Verify signatures
Validate each incoming webhook using HMAC-SHA256. See signature verification section below.
Signature verification
Every webhook request includes an X-EmacronAI CRM-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your endpoint secret.
const crypto = require('crypto');
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-outreachagent-signature'];
const secret = process.env.WEBHOOK_SECRET;
const expected = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (signature !== expected) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Event:', event.event, event.data);
res.status(200).send('OK');
});import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-EmacronAI CRM-Signature', '')
secret = os.environ['WEBHOOK_SECRET'].encode()
body = request.get_data()
expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(401)
event = request.get_json()
print(f"Event: {event['event']}", event['data'])
return 'OK', 200Event reference
otp.verifiedDescription
Fired when a user successfully verifies an OTP.
Example payload
{
"event": "otp.verified",
"timestamp": "2026-09-01T10:15:30Z",
"data": {
"request_id": "otp_abc123xyz",
"phone": "+919876543210",
"verified": true
}
}message.deliveredDescription
Fired when a WhatsApp message is confirmed delivered to the recipient's device.
Example payload
{
"event": "message.delivered",
"timestamp": "2026-09-01T10:15:32Z",
"data": {
"message_id": "wa_msg_abc123",
"to": "+919876543210",
"status": "delivered"
}
}message.readDescription
Fired when the recipient reads a WhatsApp message (blue ticks).
Example payload
{
"event": "message.read",
"timestamp": "2026-09-01T10:16:00Z",
"data": {
"message_id": "wa_msg_abc123",
"to": "+919876543210",
"status": "read"
}
}message.replyDescription
Fired when a contact replies to a WhatsApp message.
Example payload
{
"event": "message.reply",
"timestamp": "2026-09-01T10:18:00Z",
"data": {
"conversation_id": "conv_xyz789",
"contact_id": "cnt_abc123",
"phone": "+919876543210",
"body": "Yes, I would like to know more",
"type": "text"
}
}conversation.openedDescription
Fired when a new conversation is started by a contact.
Example payload
{
"event": "conversation.opened",
"timestamp": "2026-09-01T10:15:00Z",
"data": {
"conversation_id": "conv_xyz789",
"contact_id": "cnt_abc123",
"channel": "whatsapp",
"phone": "+919876543210"
}
}broadcast.completedDescription
Fired when a broadcast finishes sending to all recipients.
Example payload
{
"event": "broadcast.completed",
"timestamp": "2026-09-01T10:30:00Z",
"data": {
"broadcast_id": "bc_xyz789",
"total_recipients": 5000,
"delivered": 4920,
"failed": 80,
"read": 3200
}
}Retry policy
| Attempt | Delay | Total time |
|---|---|---|
| 1 (initial) | Immediately | 0s |
| 2 | 5 seconds | 5s |
| 3 | 30 seconds | 35s |
| 4 | 5 minutes | ~5m |
| 5 (final) | 30 minutes | ~35m |
If all 5 attempts fail (endpoint returns non-2xx or times out in >10s), the event is marked as failed and will not be retried. Check the webhook logs in your dashboard.