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énementQuand
call.startedUn appel a été décroché.
call.completedUn appel s'est terminé normalement, avec transcription et résumé.
call.failedUn appel a échoué ou n'a pas été décroché.
ask.answeredUne personne a répondu à une question.
ask.expiredUne question a expiré sans réponse.
contact.createdUn 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 :

  1. Lis le corps brut de la requête, avant tout parsing JSON.
  2. Recalcule le HMAC et compare-le à v1 en temps constant.
  3. Rejette l'événement si t s'é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.