Webhooks
Webhooks let MAIA notify your server about call events in real time. The platform sends an HTTP POST request to the URL you configure, containing the event data and an HMAC signature for authenticity verification.
Events
When creating an endpoint you choose which events it subscribes to:
| Event | When it fires |
|---|---|
call.started | The conversation began: outbound — the callee picked up, inbound — the call was answered |
call.completed | The call finished and post-processing is ready (summary, goal, extracted fields) |
call.no_answer | No answer: nobody picked up within the ring timeout, or no conversation took place |
call.transferred | The agent transferred the call to a human operator |
call.failed | The call ended with an error |
lead.created | Text channel: a lead was collected (dialog goal reached) |
call.completed also carries an amd field: "voicemail" — an answering machine picked up, "bot" — a robot was on the other end, null — a regular conversation. Integrations can use it to reschedule unreachable contacts automatically.
Configuration
Webhooks are configured per project in the dashboard at dashboard.maia-ai.com. For each project, specify:
- Webhook URL — the address of your HTTPS endpoint that MAIA will POST to.
- Secret — the string used to sign requests (HMAC). Keep it safe and use it to verify the signature.
Configure webhooks separately for each project in the dashboard. Knowledge base management is also available through the public REST API.
Example payload
The request body is JSON. The field set may expand, so write your code to tolerate additional fields.
{
"id": "delivery_uuid",
"event": "call.completed",
"created_at": "2026-07-02T10:00:00+00:00",
"data": {
"call_id": "call_uuid",
"project_id": "project_uuid",
"contact_id": "contact_uuid",
"direction": "outbound",
"phone": "+79001234567",
"status": "completed",
"duration_sec": 124,
"goal_reached": true,
"summary": "Booked for Thursday, 3 pm",
"extracted": { "name": "Anna", "datetime": "2026-07-03T15:00" },
"amd": null
}
}id— the delivery identifier (use it to deduplicate retries).data.call_id— the call identifier (same as inGET /v1/public/calls/{call_id}, where the transcript and recording are available).data.goal_reached/data.summary/data.extracted— post-processing results.data.amd—"voicemail"/"bot"/null(see above).
For call.started and call.transferred the data object is shorter: identifiers, direction, phone; transfers also include transfer_to and reason.
Verifying the HMAC signature
Every request includes an HMAC signature in a header. Compute the HMAC over the raw request body using your secret and compare the result with the signature from the header. Compare signatures with a constant-time function to resist timing attacks.
Verify the signature against the raw request body bytes, before any parsing or JSON reformatting. Parsing and re-serializing can change the bytes and break verification.
Pseudocode:
import hmac, hashlib
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature_header)If the signature does not match, return 401 and do not process the request.
Best practices
- Respond 2xx quickly. Return
200/2xximmediately upon receipt, and run heavy processing asynchronously (queue, background job). A slow response may cause a timeout and redelivery. - Idempotency. The same call may be delivered more than once (retries on network failures). Deduplicate by the call
idso you don’t process one event twice. - Always verify the signature. Do not trust the request body without a successful HMAC check.
- Use HTTPS. Your webhook endpoint must be reachable over HTTPS.
See also
- REST API — fetch call details by
id. - Authentication — API keys and scopes.