AI-powered WhatsApp agents for your business — AI WhatsApp agents — Start Now
WebhooksReal-time event delivery
Webhooks
ConvoAI pushes real-time event notifications to your server via HTTP POST requests whenever something happens — a new message, a contact update, a handoff request. Register a webhook endpoint once and receive every event automatically.
How It Works
When an event occurs in ConvoAI, the platform sends an HTTP POST request to your registered endpoint URL with a JSON body describing the event. Your server must respond with a 2xx status within 5 seconds to acknowledge receipt.
ConvoAI Platform
→
HTTP POST + HMAC Signature
→
Your Server
→
2xx Response
Registering a Webhook
Register a webhook endpoint for a specific agent. Each agent can have multiple webhook registrations, each listening to different event subsets.
Every webhook delivery includes an X-ConvoAI-Signature header. This is a HMAC-SHA256 hex digest of the raw request body, keyed with your webhook secret.
Always verify the signature before processing a webhook payload. Reject requests where the signature is missing or invalid.
import hmac
import hashlib
from django.http import HttpResponse, HttpResponseForbidden
WEBHOOK_SECRET = "your-webhook-secret"
def convoai_webhook(request):
# 1. Get the signature from the header
sig_header = request.headers.get("X-ConvoAI-Signature", "")
if not sig_header.startswith("sha256="):
return HttpResponseForbidden()
received_sig = sig_header[7:] # strip "sha256=" prefix# 2. Compute expected signature from raw body
expected_sig = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
request.body, # use the RAW bytes, not decoded string
hashlib.sha256
).hexdigest()
# 3. Compare — use compare_digest to prevent timing attacks
if not hmac.compare_digest(expected_sig, received_sig):
return HttpResponseForbidden()
# 4. Safe to parse and process
import json
payload = json.loads(request.body)
handle_event(payload)
return HttpResponse(status=200)
Verify signature — Node.js (Express)
const crypto = require('crypto');
const express = require('express');
const app = express();
const WEBHOOK_SECRET = process.env.CONVOAI_WEBHOOK_SECRET;
// Use express.raw() to preserve the raw body for signature checking
app.post('/webhooks/convoai', express.raw({ type: 'application/json' }), (req, res) => {
const sigHeader = req.headers['x-convoai-signature'] || '';
if (!sigHeader.startsWith('sha256=')) {
return res.status(403).send('Missing signature');
}
const receivedSig = sigHeader.slice(7);
const expectedSig = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body) // req.body is a Buffer here
.digest('hex');
const valid = crypto.timingSafeEqual(
Buffer.from(expectedSig, 'hex'),
Buffer.from(receivedSig, 'hex')
);
if (!valid) return res.status(403).send('Invalid signature');
const payload = JSON.parse(req.body.toString());
handleEvent(payload);
res.sendStatus(200);
});
Retry Policy
If your endpoint returns a non-2xx response or times out (5s), ConvoAI retries delivery with exponential backoff. Each retry carries the same id in the envelope so you can deduplicate.
Attempt
Delay after previous failure
Cumulative time
1 (original)
—
0s
2
30 seconds
~30s
3
5 minutes
~5m
4
30 minutes
~35m
5 (final)
2 hours
~2h 35m
After 5 failed attempts the event is marked failed and delivery stops. You can view delivery logs and manually replay events from the ConvoAI dashboard under Settings → Webhooks.
Deduplication tip: Always process webhook events idempotently. Store the event id and skip processing if you've seen it before. This protects against duplicate deliveries during retries or any transient failures on our side.
Request Headers
Header
Description
Content-Type
Always application/json; charset=utf-8
X-ConvoAI-Signature
HMAC-SHA256 signature: sha256={hex_digest}
X-ConvoAI-Event
The event type string, e.g. message.received
X-ConvoAI-Delivery
Unique delivery attempt ID — different from event ID
User-Agent
ConvoAI-Webhook/1.0
Testing Webhooks
Use the test endpoint to send a synthetic event to your registered URL without needing a real conversation.
POST/v1/agents/{agent_id}/webhooks/{webhook_id}/test/Send test event
Test events have "livemode": false in the envelope. IDs use a test_ prefix. For local development, use a tunnel tool like ngrok or Tunnelmole to expose your local server.