Aller au contenu

Webhooks

Vos alertes en JSON signé dans votre propre système, quelques minutes après leur apparition dans votre compte.

Configuration

Ajoutez sous Intégrations un webhook avec l'adresse https de votre système. Votre secret de signature (whsec_...) ne s'affiche qu'une fois ; conservez-le dans votre système. Le bouton Envoyer un test vous envoie aussitôt un événement de type endpoint.test.

Le webhook reçoit les mêmes alertes que votre compte, selon vos règles de notification, filtrées selon la gravité et les thèmes choisis pour le webhook.

La requête

Un POST par alerte, avec un corps JSON et ces en-têtes :

Content-Type
application/json; charset=utf-8
User-Agent
Checked.be-Webhooks/1.0 (+https://checked.be/nl/developers/webhooks)
webhook-id
Identifiant unique du message, égal au champ id. Identique à chaque nouvelle tentative : utilisez-le pour ignorer les doublons.
webhook-timestamp
Moment de cette tentative, en secondes depuis 1970 (UTC).
webhook-signature
La signature : v1, suivi du HMAC-SHA256 en base64. Voir ci-dessous.

Exemple

Une alerte sur une entreprise fictive, exactement comme Checked l'envoie :

{
  "id": "msg_5f0c2d6e9b1a4c7d8e3f2a1b0c9d8e7f",
  "type": "alert.created",
  "api_version": "2026-09-25",
  "created_at": "2026-09-25T07:38:12Z",
  "language": "fr",
  "data": {
    "alert": {
      "id": 184223,
      "kind": "bs_capital_change",
      "label": "Opération sur le capital",
      "signal": "kapitaal",
      "theme": "financieel",
      "theme_label": "Finances",
      "severity": "med",
      "severity_label": "moyenne",
      "title": "Kapitaalverhoging van € 250.000",
      "body": "Het kapitaal stijgt van € 18.550 naar € 268.550.",
      "created_at": "2026-09-25T07:36:12Z"
    },
    "company": {
      "enterprise_number": "0999999999",
      "enterprise_number_display": "0999.999.999",
      "name": "Voorbeeld NV",
      "dossier_url": "https://checked.be/fr/c/voorbeeld-nv-0999999999"
    },
    "alerts_url": "https://checked.be/fr/alerts"
  }
}

Champs

type
alert.created (une alerte), digest.created (le résumé quotidien ou hebdomadaire, si vous le cochez) ou endpoint.test (le bouton de test). Ignorez les types inconnus : d'autres peuvent s'ajouter.
api_version
La version de ce format. Des champs peuvent s'ajouter ; les champs existants ne changent pas au sein d'une version.
language
La langue des libellés et des liens (nl, fr ou en), selon le réglage du webhook. Le titre et le texte d'une alerte sont en néerlandais, comme dans votre compte.
data.alert.kind
Le type d'alerte, un code fixe. label en est le nom, signal et theme la ligne et le thème des règles de notification (null pour un résumé de plusieurs actes).
data.alert.severity
low, med ou high , avec severity_label dans la langue choisie.
data.company
Le numéro d'entreprise (10 chiffres et dans l'écriture usuelle), le nom (null s'il est inconnu) et le lien vers le dossier sur Checked.be.

Un webhook ne reçoit que ce que l'alerte montre aussi dans votre compte : pas d'adresse e-mail ni d'autres données du compte.

Vérifier la signature

Checked signe selon Standard Webhooks, vous pouvez donc aussi utiliser l'une de leurs bibliothèques. Soi-même :

  1. Prenez la clé : la partie après whsec_, décodée en base64.
  2. Calculez le HMAC-SHA256 de {webhook-id}.{webhook-timestamp}.{body}, avec le corps exactement tel que reçu.
  3. Comparez v1, suivi de sa valeur base64, en temps constant, avec chaque valeur de webhook-signature (séparées par des espaces).
  4. Refusez un timestamp qui s'écarte de plus de cinq minutes de votre horloge.
Node.js
import crypto from "node:crypto";

export function verify(secret, headers, body) {
  const id = headers["webhook-id"];
  const ts = headers["webhook-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = "v1," + crypto.createHmac("sha256", key)
    .update(`${id}.${ts}.${body}`).digest("base64");
  return headers["webhook-signature"].split(" ").some(sig =>
    sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
}
Python
import base64, hashlib, hmac, time

def verify(secret: str, headers: dict, body: bytes) -> bool:
    msg_id = headers["webhook-id"]
    ts = headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > 300:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{ts}.".encode() + body
    expected = "v1," + base64.b64encode(
        hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return any(hmac.compare_digest(sig, expected)
               for sig in headers["webhook-signature"].split(" "))
C#
using System.Security.Cryptography;
using System.Text;

static bool Verify(string secret, string id, string ts, string signatures, string body)
{
    if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(ts)) > 300) return false;
    var key = Convert.FromBase64String(secret["whsec_".Length..]);
    var hash = HMACSHA256.HashData(key, Encoding.UTF8.GetBytes($"{id}.{ts}.{body}"));
    var expected = Encoding.UTF8.GetBytes("v1," + Convert.ToBase64String(hash));
    return signatures.Split(' ').Any(s =>
        CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(s), expected));
}

Réponses et nouvelles tentatives

  • Répondez en 10 secondes avec un statut 2xx. Faites le travail lourd plutôt ensuite.
  • Toute autre réponse, ou l'absence de réponse, entraîne une nouvelle tentative, chaque fois plus tard : après 1, 5, 15 et 30 minutes, puis après 1 à 12 heures. Un message est tenté jusqu'à 6 fois et expire après 3 jours.
  • Les messages arrivent dans l'ordre : tant que le plus ancien n'est pas livré, les suivants attendent. Un en-tête Retry-After est respecté (jusqu'à une heure).
  • Après 10 échecs consécutifs, le webhook est désactivé et son propriétaire reçoit un e-mail. Une réponse 410 Gone le désactive sans nouvelle tentative.
  • Les redirections (3xx) ne sont pas suivies. L'adresse doit utiliser https et être publique ; les adresses d'un réseau interne sont refusées, même si le nom y pointe plus tard.
  • Chaque tentative figure dans le journal de livraison du webhook, avec le statut et la durée.