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.

Important: Always verify the X-EmacronAI CRM-Signature header to confirm events are from EmacronAI CRM and not a third party.

Setup

  1. 1

    Create an endpoint

    Add a POST endpoint to your server that accepts JSON. It must respond with HTTP 200 within 10 seconds.

  2. 2

    Register in dashboard

    Go to Settings → Webhooks → Add Endpoint. Paste your URL and select the events you want to receive.

  3. 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. 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.

Node.js (Express)
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');
});
Python (Flask)
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', 200

Event reference

otp.verified

Description

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.delivered

Description

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.read

Description

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.reply

Description

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.opened

Description

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.completed

Description

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

AttemptDelayTotal time
1 (initial)Immediately0s
25 seconds5s
330 seconds35s
45 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.