Webhooks
Subscribe to ConvoMem events to react to new memories and conversation state changes in real time.
Webhooks let your systems react the moment ConvoMem fires an event — without polling. Webhook endpoints are configured in the dashboard (Settings → Webhooks). They cannot be managed via an org API key.
Each endpoint belongs to one agent and receives only that agent's events. Choose the events to subscribe to when you create the endpoint.
Events
Every event's data includes customerId.
Customers
| Event | Fires when | Key data fields |
|---|---|---|
customer.created | A customer is created, explicitly or automatically on first contact | customerId, name, email, phone, externalId |
Capture
| Event | Fires when | Key data fields |
|---|---|---|
capture.started | A capture job begins processing | captureId, customerId, conversationId |
capture.completed | A capture job finishes | captureId, customerId, conversationId, saved, skipped |
capture.failed | A capture job fails after its final retry | captureId, customerId, conversationId, error |
memory.captured | A capture saved at least one new memory | captureId, customerId, conversationId, saved, skipped |
Memory lifecycle
These events never include memory text — fetch the memory if you need its content.
| Event | Fires when | Key data fields |
|---|---|---|
memory.created | A memory is created | eventId, memoryId, customerId, version, operation, cause, status, occurredAt |
memory.updated | A memory changes | same as above |
memory.superseded | A newer memory replaces this one | same as above, plus supersededById |
memory.deleted | A memory is deleted | same as above |
commitment.overdue | A commitment made to the customer passes its due date | commitmentId, insightId, customerId, conversationId, dueAt, humanAgentId |
Conversations
| Event | Fires when | Key data fields |
|---|---|---|
conversation.completed | A conversation is ended, including when it idle-closes | conversationId, customerId, outcome |
conversation.escalated | A conversation is escalated | conversationId, customerId, reason |
Leads
Fired by lead playbooks configured in the dashboard.
| Event | Fires when | Key data fields |
|---|---|---|
lead.created | A playbook detects a new lead in a conversation | id, templateId, templateName, customerId, conversationId, score, priority, status |
lead.updated | A lead is re-evaluated or edited | same as above |
lead.status_changed | A lead's status changes | same as above |
lead.disqualified | A lead is marked DISQUALIFIED | same as above |
Event payload
Every delivery is a POST with X-ConvoMem-Signature and X-ConvoMem-Event headers:
{
"event": "memory.captured",
"createdAt": "2026-06-17T10:00:00.000Z",
"data": {
"customerId": "f3b39d1b-2522-4a00-98df-8cdde783ba12",
"conversationId": "87c53d10-85f8-4447-920f-b1e847c23a1a",
"captureId": "0b6f4c1e-3d2a-4e8b-a7c9-5f1d2e3b4a6c",
"saved": 3,
"skipped": 0
}
}Verifying signatures
The signature is sha256=<HMAC-SHA256(rawBody, webhookSecret)>. Always verify before
processing — anyone who learns your URL could otherwise POST to it.
Compute the HMAC over the raw bytes of the request body, not a re-serialized object.
import crypto from 'node:crypto'
function verifyWebhook(
rawBody: Buffer | string,
signatureHeader: string,
secret: string,
): boolean {
const sig = signatureHeader.replace('sha256=', '')
const digest = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
const a = Buffer.from(sig, 'hex')
const b = Buffer.from(digest, 'hex')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}import hmac, hashlib
def verify_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = signature_header.replace("sha256=", "")
computed = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, computed)Unverified webhook payloads should be rejected — anyone who learns your URL could otherwise POST to it.
Delivery semantics
- Retries — non-
2xxor timeout triggers up to 3 retries with exponential backoff. - Idempotency — deliveries are at-least-once and can repeat. De-duplicate on
eventIdformemory.*events,captureIdfor capture events, and(event, conversationId)for conversation events. - SSRF protection — destination URLs must be
https://. Loopback, private, and cloud metadata addresses are rejected. - Timeout — your endpoint must respond within 10 s.
Related
- Capture — triggers
capture.*andmemory.captured - Conversations — triggers
conversation.completedandconversation.escalated - Authentication — API key scopes and security model