business-api/Webhooks

Webhooks

Recevez, vérifiez et dédupliquez les événements de l’API YAOKA.

Configuration

Dans Portail développeur → Webhooks, ajoutez une URL HTTPS publique et sélectionnez les événements utiles. Copiez le secret whsec_... lors de son unique affichage et stockez-le dans un gestionnaire de secrets.

En-têtes envoyés

X-Yaoka-Signature: t=1785801600,v1=4e8f...
X-Yaoka-Key-Version: 1
X-Yaoka-Event-Id: evt_...
User-Agent: YAOKA-Webhooks/1.0

Le timestamp et la signature sont regroupés dans X-Yaoka-Signature. Le type d’événement se trouve dans le champ event du corps JSON.

Corps d’un événement

{
  "apiVersion": "v1",
  "schemaVersion": 1,
  "sourceProduct": "core",
  "event": "invoice.finalized",
  "eventId": "evt_...",
  "organizationId": "org_...",
  "timestamp": 1785801600,
  "occurredAt": "2026-08-04T00:00:00.000Z",
  "subject": { "type": "invoice", "id": "inv_..." },
  "data": { "invoiceId": "inv_...", "number": "F-2026-0001" }
}

La livraison est « au moins une fois ». Dédupliquez vos traitements avec eventId ou X-Yaoka-Event-Id avant de modifier vos données.

Vérifier la signature

Calculez le HMAC SHA-256 de timestamp + "." + rawBody avec le secret, puis comparez les empreintes en temps constant. Utilisez impérativement le corps HTTP brut avant tout parsing JSON.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyYaokaWebhook(
  rawBody: string,
  header: string,
  secret: string,
) {
  const values = Object.fromEntries(
    header.split(",").map((part) => part.split("=", 2)),
  );
  const timestamp = values.t;
  const received = values.v1;
  if (!timestamp || !received) return false;

  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  return (
    expected.length === received.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(received))
  );
}

Répondez en 2xx dès que l’événement est validé et placé dans votre file de traitement. Les erreurs sont retentées jusqu’à 6 fois, après environ 1 min, 5 min, 30 min, 2 h, 6 h puis 12 h.

Événements publics

RessourceÉvénements
Clientscustomer.created, customer.updated
Devisquote.created, quote.updated, quote.finalized, quote.converted
Facturesinvoice.created, invoice.updated, invoice.finalized, invoice.paid, invoice.overdue
Avoirscredit_note.created

Checklist du handler

  1. Lire le corps brut.
  2. Vérifier X-Yaoka-Signature et refuser un timestamp âgé de plus de 5 minutes.
  3. Vérifier que eventId n’a pas déjà été traité.
  4. Placer le travail métier dans une file.
  5. Répondre rapidement en 2xx.