POST /v1/webhook/events
Emit a CRM event from an external system — it lands on the contact’s ticket and writes its values onto the ticket and the contact.
/v1/webhooks (plural), which manages the outbound subscriptions Keebai delivers to you.
key is what you send as type. Create it in the portal under CRM → Event types; the same screen shows this endpoint, generates an API key for it, and renders the exact body for that type.Endpoint
Required scope
crm:events:write
Headers
Body
ticket_id, then chat_user_id, then customer_id, then phone — and stops at the first one present.
Example request
Response
202 Accepted
202, not 201: the event is recorded synchronously, but the work it triggers downstream — projecting it onto the conversation timeline, pushing it to the portal in realtime, firing crm.ticket.updated — completes after the response.
duplicated is true when the idempotency_key had already been used. The original event comes back untouched and nothing is written a second time.
400 Bad Request
A malformed body: missingtype, a phone outside 6–20 characters, an occurred_at that is not ISO 8601, a malformed ObjectId, or a property the endpoint does not accept — including company and project, which come from the token.
401 Unauthorized
Missing, invalid, revoked, or expired token.403 Forbidden
The token does not have thecrm:events:write scope.
422 Unprocessable Entity
The request was well-formed but the event was not recorded. Branch onerror.code:
429 Too Many Requests
You hit the ingest rate limit. See below.Rate limits
This route carries its own limits, well above the platform defaults, because an ingest exhausts a quota sized for interactive use in an afternoon:Operational notes
- Always send an
idempotency_key. Network retries are cheap and duplicated timeline entries are not. Use whatever identifier the event already has in your system — an order id, a transaction id, a row id. datawrites through. The values you send are not only stored on the event: each field the type declares is written onto the ticket or onto the contact, depending on its scope, and overwrites whatever was there. See CRM events.- Keys outside the declared fields are kept but not written. They stay in the event’s payload and reach nothing else. Only fields associated with the type reach the ticket or the contact.
- A
422is not retryable except forCONTACT_LOOKUP_FAILED. Everything else needs a fix on your side or in the CRM configuration. Dead-letter the payload and alert rather than looping. occurred_atis when it happened, not when you sent it. Backfilling a batch with the real timestamps puts the events in the right order on the timeline.
Authorizations
Personal Access Token con prefijo kbai_pk_. Generar desde el portal con permiso developer.manage_tokens.
Body
Key del tipo de evento configurado en el portal, por ejemplo custom.pedido_confirmado.
Teléfono del contacto en formato canónico (sólo dígitos, con código de país y sin +). Alternativa a chat_user_id / customer_id / ticket_id.
ObjectId del contacto (chat user)
ObjectId del cliente del CRM
ObjectId del ticket. Si viene, el evento se adjunta a ese ticket y no se resuelve el contacto.
Valores de los campos asociados al tipo de evento, indexados por la key del campo. Los requeridos deben venir y cada valor debe respetar el tipo del campo.
Texto corto del evento. Por defecto, el nombre del tipo.
Momento en que ocurrió, ISO 8601. Por defecto, ahora.
Identificador único del evento en tu sistema. Reenviar el mismo valor no duplica el evento.