Receive events by webhook
Register a webhook, learn the shape of each delivery, check the HMAC signature and handle retries.
Updated on November 12, 2026
With a webhook, Cognizy tells your system the moment something happens: a new conversation, a contact created, a deal won, a payment confirmed. No need to keep polling the API.
1. Register the webhook
On screen: Settings › Integrations › API and webhooks, Webhooks tab, New webhook button. Enter the URL (https) and tick the events.
Through the API (webhooks:manage permission):
curl -X POST https://api.cognizy.ai/api/v1/public/webhooks \
-H "Authorization: Bearer $COGNIZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/webhooks/cognizy","events":["contact.created","deal.won"],"description":"Internal CRM"}'The response carries the webhook secret only once. Keep it: it's how you check the signature.
2. What reaches your system
Each delivery is a POST with a JSON body:
{
"id": "dlv_8f2c1a",
"event": "deal.won",
"timestamp": "2026-11-12T14:03:22.418Z",
"data": {
"id": "deal_123",
"title": "Annual plan",
"value": 4800,
"stage": "WON",
"contactId": "contact_456"
}
}And these headers:
Content-Type: application/json
User-Agent: Cognizy-Webhook/1.0
X-Cognizy-Event: deal.won
X-Cognizy-Delivery: dlv_8f2c1a
X-Cognizy-Webhook-Id: wh_123
X-Cognizy-Signature: t=1762956202,v1=5f0c…data is the record behind the event (the deal, the contact, the conversation…), in the same shape as the API. The fields above are an example.
3. Check the signature
The signature is HMAC-SHA256(secret, t + "." + body), in hex, in v1. Always check it before using the event: otherwise anyone who finds the URL can send fake events.
- Use the raw body, exactly as received. If your framework already parsed it, the signature won't match.
- Reject when
tis more than 5 minutes old (stops replays of an old delivery). - While the secret rotates, the header carries two
v1values for 24 h: accept if either matches.
With the SDK (Node and Express):
import express from 'express'
import { verifyWebhookSignature } from '@cognizy/sdk'
const app = express()
app.post('/webhooks/cognizy', express.raw({ type: 'application/json' }), (req, res) => {
const ok = verifyWebhookSignature({
secret: process.env.COGNIZY_WEBHOOK_SECRET!,
body: req.body, // raw Buffer
header: req.header('x-cognizy-signature'),
})
if (!ok) return res.status(400).send('invalid signature')
const event = JSON.parse(req.body.toString('utf8'))
// handle the event (queue it if it takes long)
res.status(200).send('ok')
})Without the SDK, in Python:
import hashlib, hmac, time
def valid_signature(secret: str, body: bytes, header: str, tolerance=300) -> bool:
parts = [p.split("=", 1) for p in header.split(",") if "=" in p]
t = next((v for k, v in parts if k == "t"), None)
signatures = [v for k, v in parts if k == "v1"]
if not t or not signatures or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, v) for v in signatures)4. Answer fast and handle repeats
- Answer with a 2xx status within 15 seconds. If the work takes long, queue the event and answer right away.
- Without a 2xx, Cognizy tries again, up to 5 attempts in all: after 10 s, 1 min, 5 min and 30 min.
- The same delivery may arrive more than once. Use the
id(or theX-Cognizy-Deliveryheader) to process it only once.
To list deliveries and replay a failed one: GET /v1/public/webhooks/{id}/deliveries and POST /v1/public/webhooks/deliveries/{deliveryId}/replay. To test the URL, POST /v1/public/webhooks/{id}/test sends a webhook.test event.
Rotating the secret
POST /v1/public/webhooks/{id}/rotate-secret creates a new secret. The old one stays valid for 24 h, so you can update your system without losing events.
Events
| Area | Events |
|---|---|
| Conversations | conversation.created, conversation.updated, conversation.closed, conversation.assigned, conversation.unassigned, conversation.ai_handoff, conversation.sla_breached, conversation.tag.added, conversation.tag.removed, csat.received |
| Messages | message.created, message.received |
| Contacts | contact.created, contact.updated, contact.deleted, contact.tag.added, contact.tag.removed, contact.scored, contacts.imported |
| Sales | deal.created, deal.updated, deal.stage_changed, deal.won, deal.lost, deal.deleted, proposal.sent, proposal.viewed, proposal.accepted, proposal.rejected |
| Payments | payment.created, payment.completed, payment.receipt_received |
| Scheduling | appointment.created, appointment.rescheduled, appointment.cancelled |
| Tickets | ticket.created, ticket.assigned, ticket.updated, ticket.resolved, ticket.closed, ticket.reopened, ticket.sla_breached |
| Tasks | task.created, task.moved, task.completed, task.updated, task.assigned |
| Direct sends | whatsapp.message.status, email.message.status |
The full, always current list shows up in the New webhook window and on the webhooks routes of the reference.