API public et webhooks
L'API REST permet à votre CRM, au backend de votre site ou à votre propre bot de travailler avec les leads et les dialogues cenaly.com comme le fait le panneau : lire les fiches, modifier les propriétés, envoyer des événements, répondre dans les dialogues. Les webhooks, à l'inverse, informent votre serveur des nouveaux leads, événements et messages.
Ce qu'il faut pour démarrer#
| Quoi | Où |
|---|---|
| Rôle | propriétaire du compte — la clé agit en son nom ; les employés ne voient pas les clés |
| Section | chat activé (les leads et les dialogues y vivent) |
| Clé | Intégrations → API et webhooks → Clés → Créer une clé |
| Adresse de l'API | https://api.cenaly.com/public-api/v1 |
| Référence des méthodes | api.cenaly.com/public-api/v1/docs |
| OpenAPI 3.1 | api.cenaly.com/public-api/v1/openapi.json |
Étape 1. Clé et droits#
- Ouvrez Intégrations et cliquez sur API et webhooks en haut de la page.
- Dans l'onglet Clés, cliquez sur Créer une clé, saisissez un nom (« CRM », « Backend du site », « Bot Telegram »).
- Choisissez un ensemble de droits ou cochez les droits un par un, et en cas de plusieurs établissements — à quels établissements la clé est autorisée, ainsi que la durée.
- Copiez le secret
cnk_…. Il n'est affiché qu'une fois : nous ne conservons que son empreinte (SHA-256).
| Droit | Ce qu'il permet |
|---|---|
contacts:read |
liste, recherche et fiche du lead, propriétés |
contacts:write |
modifier les propriétés et les tags, fusionner, supprimer sur demande de la personne concernée |
events:read |
fil des événements du lead |
events:write |
envoyer des événements (achat, inscription, tout événement qui vous est propre) |
conversations:read |
dialogues et messages |
conversations:write |
réponse d'un opérateur, note, statut, responsable, libellés |
messages:send |
message au lead dans son dernier dialogue |
segments:read |
segments enregistrés et leurs participants |
Ensembles : CRM / backend (leads, propriétés, événements, lecture des dialogues et des segments), Votre bot (lit les leads, répond dans les dialogues, écrit aux leads), Lecture seule, Tout.
- Droits modifie à la volée les droits et les établissements de la clé — l'intégration continue de fonctionner.
- Réémettre délivre un nouveau secret ; l'ancien cesse de fonctionner immédiatement, les droits et le journal restent.
- Révoquer — immédiatement et définitivement, la requête suivante recevra
401.
La clé est transmise par l'en-tête Authorization: Bearer cnk_… (ou X-Api-Key: cnk_…). Conservez-la uniquement sur le serveur — pas dans le navigateur ni dans l'application mobile.
Étape 2. Premières requêtes#
KEY=cnk_ваш_ключ
API=https://api.cenaly.com/public-api/v1
# ce que la clé permet : droits, établissements, limite
curl -H "Authorization: Bearer $KEY" "$API/me"
# leads, les plus récents en premier (20 par page)
curl -H "Authorization: Bearer $KEY" "$API/contacts?limit=20"
# recherche par nom, e-mail ou téléphone
curl -H "Authorization: Bearer $KEY" "$API/contacts?q=anna"
La réponse est toujours une enveloppe :
{
"meta": { "status": 200, "requestId": "…", "nextCursor": "WyIyMDI2LTEwLTA0…" },
"data": { "total": 128, "contacts": [ { "id": "…", "status": "lead", "name": "Анна", "updatedAt": "2026-10-04T09:12:00Z" } ] }
}
La page suivante — la même requête avec cursor=<meta.nextCursor> ; pas de nextCursor — il n'y a plus de pages. Erreur — {"meta": {...}, "error": {"code": "…"}}.
Établissement. Si un seul établissement est accessible à la clé, il n'est pas nécessaire de l'indiquer. S'il y en a plusieurs — ajoutez location=<id de l'établissement> (liste — GET /v1/locations), sinon la réponse sera 400 location_required.
Propriétés et événements selon votre user_id. Il n'est pas obligatoire de conserver notre id de lead : ajoutez ?by=user_id, et l'adresse deviendra votre identifiant utilisateur. S'il n'y a pas encore de lead avec un tel user_id — nous le créerons.
# propriétés : opérations update_or_create, set_once, add, delete, append, union, exclude
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"ops":[{"op":"update_or_create","key":"$email","value":"anna@example.com"},
{"op":"add","key":"orders","value":1},
{"op":"union","key":"interests","value":"yoga"}]}' \
"$API/contacts/u-1001/props?by=user_id"
# événements par lots (jusqu'à 100), une répétition avec le même id n'est comptée qu'une fois
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"events":[{"id":"ord-5531","userId":"u-1001","name":"order_paid","props":{"sum":45,"currency":"GEL"}}]}' \
"$API/events"
# réponse dans un dialogue et changement de statut
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"text":"Здравствуйте! Заказ передан курьеру."}' "$API/conversations/<id>/reply"
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"status":"resolved"}' "$API/conversations/<id>/status"
L'écriture passe par la même file d'attente que les modifications depuis le panneau : les changements sont appliqués en quelques secondes, et la réponse à une écriture par user_id — 202 avec pending. Dans la fiche du lead, l'auteur de la modification apparaît comme « API : <nom de la clé> ».
Méthodes#
| Méthode | Ce qu'elle fait | Droit |
|---|---|---|
GET /v1/me, GET /v1/locations |
clé, droits, établissements | — |
GET /v1/contacts |
prospects : status (anon, lead, user), q, limit, cursor |
contacts:read |
GET /v1/contacts/{id} |
fiche : propriétés, tags, synthèse des événements | contacts:read |
POST /v1/contacts/{id}/props |
propriétés par opérations (jusqu'à 250) ou {"props":{…}} |
contacts:write |
POST / DELETE /v1/contacts/{id}/tags |
tags du prospect | contacts:write |
POST /v1/contacts/merge |
fusion de deux fiches : aperçu, puis "apply": true |
contacts:write |
POST /v1/contacts/{id}/erase |
suppression sur demande de la personne concernée ("confirm": true) |
contacts:write |
GET /v1/contacts/{id}/events |
fil des événements du prospect | events:read |
POST /v1/contacts/{id}/events, POST /v1/events |
événements (jusqu'à 100 à la fois) | events:write |
GET /v1/contacts/{id}/conversations |
conversations du prospect | conversations:read |
POST /v1/contacts/{id}/messages |
écrire au prospect dans sa dernière conversation | messages:send |
GET /v1/conversations, /{id}, /{id}/messages |
conversations et messages | conversations:read |
POST /v1/conversations/{id}/reply |
réponse d'un opérateur ou note ("type": "note") |
conversations:write |
POST /v1/conversations/{id}/status, /assign, /tags |
statut, responsable, libellés | conversations:write |
GET /v1/segments, GET /v1/segments/{id}/contacts |
segments et leurs membres | segments:read |
Les champs, exemples et codes d'erreur de chaque méthode se trouvent dans le référentiel.
Étape 3. Webhooks#
Les webhooks se configurent au niveau de l'établissement : Chat → Paramètres → Webhooks — adresse https, événements et secret de signature. Aux événements de conversation (nouvelle conversation, nouveau message, statut, évaluation, libellés) se sont ajoutés les événements de prospect :
| Événement | Quand il arrive |
|---|---|
lead.created |
un nouveau prospect est apparu (contact laissé dans le chat, un formulaire, ou créé via l'API) |
lead.props_changed |
les propriétés du prospect ont changé — liste des clés dans changedProps |
lead.event_tracked |
le prospect a un événement ; par défaut tous vos événements sans les événements système ($…), la liste des noms est définie par le champ « Quels événements envoyer » |
message.goal_reached |
le prospect a atteint l'objectif du message (pop-up, envoi groupé) |
{
"event": "lead.event_tracked",
"deliveryId": "WD…",
"at": "2026-10-04T09:12:03Z",
"webhookId": "…",
"locationId": "…",
"contact": { "id": "…", "status": "user", "userId": "u-1001", "props": { "orders": 3 }, "tags": ["vip"] },
"leadEvent": { "name": "order_paid", "at": "2026-10-04T09:12:00Z", "props": { "sum": 45 } }
}
Le nom, l'e-mail et le téléphone du prospect ne figurent dans le corps que si la case « Transmettre les contacts du client » est cochée pour le webhook. Vérification de la signature — HMAC-SHA256 du corps avec le secret du webhook :
# заголовок X-Meni-Signature: sha256=<hex>
echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET"
Répondez 2xx en 5 secondes — sinon la livraison sera répétée ; après une longue série d'échecs, le webhook est désactivé, et cela se voit dans les paramètres. Filtrez les doublons par deliveryId.
Limites#
- 60 requêtes par minute par clé ; au-delà —
429avec l'en-têteRetry-After. Le reste — dansX-RateLimit-*. - Jusqu'à 200 enregistrements par page (50 par défaut), jusqu'à 100 événements par lot, jusqu'à 250 opérations sur les propriétés par requête, jusqu'à 30 tags à la fois.
- Jusqu'à 20 clés actives par compte.
- Le journal des appels de la clé est conservé 30 jours : heure, méthode, modèle de chemin (sans id ni corps), code de réponse, durée.
- Les événements envoyés via l'API sont enregistrés dans la fiche du prospect (fil, synthèse, segments), mais pas dans le journal brut de l'analyse des visites.
- On ne peut écrire à un prospect que dans une conversation existante : pas de conversation — réponse
409 no_conversation.
Dépannage#
| Réponse | Ce que cela signifie | Que faire |
|---|---|---|
401 api_key_missing / api_key_invalid |
la clé est absente de la requête, ou elle est incorrecte, révoquée ou expirée | vérifiez l'en-tête ; si nécessaire, réémettez la clé |
403 scope_required |
la clé n'a pas le droit nécessaire | Droits de la clé — ajoutez le droit |
400 location_required |
plusieurs établissements sont accessibles à la clé | ajoutez location=<id> |
404 location_not_found |
l'établissement ne fait pas partie de ceux de la clé | vérifiez avec GET /v1/locations |
404 not_found |
le prospect ou la conversation n'existe pas | pour vos propres id, utilisez ?by=user_id |
429 rate_limited |
plus de 60 requêtes par minute | attendez Retry-After secondes, regroupez les événements par lots |
502 upstream_error |
panne temporaire | répétez la requête plus tard |
Chaque requête est visible dans l'onglet Journal des appels — avec le code de réponse et le nom de la clé.
Questions fréquentes#
Peut-on appeler l'API depuis un navigateur ? Non : la clé donne accès à tous les prospects de l'établissement. Appelez l'API depuis votre serveur.
Quelle est la différence entre user_id et l'id du prospect ? L'id du prospect est notre identifiant de fiche. user_id est votre identifiant utilisateur (numéro dans le CRM, id du compte sur le site) ; avec ?by=user_id, la fiche peut être adressée par celui-ci.
Que se passe-t-il en cas d'envoi répété d'un événement ? Un événement avec le même id n'est compté qu'une fois — vous pouvez répéter la requête sans crainte en cas d'erreur réseau.
Faut-il une clé distincte pour chaque établissement ? Non : une seule clé peut fonctionner avec plusieurs établissements, l'établissement est choisi par le paramètre location.
Sections liées#
- Chat avec les clients — conversations, prospects et paramètres des webhooks
- Demandes — flux entrant unifié
- API et MCP de votre standard téléphonique — API de téléphonie distincte
- Intégrations