Documentation
Webhooks
Reçois les événements d'appel et vérifie leur signature.
Les webhooks préviennent ton serveur de ce qui s'est passé pendant un appel, sans que tu aies à interroger l'API. Crée un endpoint dans l'application (Connecteurs et API) ou avec POST /v1/webhook-endpoints : tu reçois son secret de signature une seule fois.
Événements
| Événement | Quand |
|---|---|
call.started | Un appel a été décroché. |
call.completed | Un appel s'est terminé normalement, avec transcription et résumé. |
call.failed | Un appel a échoué ou n'a pas été décroché. |
ask.answered | Une personne a répondu à une question. |
ask.expired | Une question a expiré sans réponse. |
contact.created | Un contact a été créé. |
Chaque événement a un id (identique à chaque nouvel essai : sers-t'en pour dédoublonner), un type, un apiVersion, un createdAt et un data.object. Les schémas sont dans la référence de l'API.
Livraison
Les événements sont envoyés en POST au format JSON. Réponds par un statut 2xx en moins de 10 secondes. Les échecs sont rejoués avec un délai exponentiel pendant 24 heures au maximum. Les livraisons d'un endpoint, avec leur statut, sont listées par GET /v1/webhook-endpoints/{endpointId}/deliveries et dans l'application, où tu peux envoyer un événement de test.
Vérifier la signature
Chaque livraison porte l'en-tête :
Allomi-Signature: t=1793908800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
t est un horodatage Unix en secondes et v1 est le HMAC-SHA256 hexadécimal, avec le secret de ton endpoint pour clé, de la chaîne t + "." + corps brut. Pour vérifier :
- Lis le corps brut de la requête, avant tout parsing JSON.
- Recalcule le HMAC et compare-le à
v1en temps constant. - Rejette l'événement si
ts'écarte de plus de 5 minutes de l'heure actuelle (protection contre le rejeu).
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyAllomiSignature(secret: string, rawBody: string, header: string | null, toleranceSeconds = 300) {
if (!header) return false;
const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=", 2) as [string, string]));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const got = parts.v1 ?? "";
return got.length === expected.length && timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}
Un handler Express ou Hono :
app.post("/hooks/allomi", async (c) => {
const raw = await c.req.text();
if (!verifyAllomiSignature(process.env.ALLOMI_WEBHOOK_SECRET!, raw, c.req.header("Allomi-Signature") ?? null)) {
return c.text("invalid signature", 400);
}
const event = JSON.parse(raw);
// dédoublonne sur event.id, puis traite event.type
return c.text("ok");
});
En Python :
import hashlib, hmac, time
def verify(secret: str, raw_body: bytes, header: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t = int(parts["t"])
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
Renouveler le secret
POST /v1/webhook-endpoints/{endpointId}/rotate-secret renvoie un nouveau secret. Mets ton serveur à jour tout de suite : les livraisons sont signées immédiatement avec le nouveau.