Setup
Under Integrations, add a webhook with an https address of your system. Your signing secret (whsec_...) is shown once; keep it with your system. The Send a test button sends you an event of type endpoint.test right away.
The webhook receives the same alerts as your account, as your alert rules choose them, filtered by the severity and themes you pick for the webhook.
The request
One POST per alert, with a JSON body and these headers:
Content-Typeapplication/json; charset=utf-8User-AgentChecked.be-Webhooks/1.0 (+https://checked.be/nl/developers/webhooks)webhook-id- Unique id of the message, equal to the id field. The same on every retry: use it to ignore duplicates.
webhook-timestamp- Time of this attempt, in seconds since 1970 (UTC).
webhook-signature- The signature:
v1,followed by the base64 HMAC-SHA256. See below.
Example
An alert about a made-up company, exactly as Checked sends it:
{
"id": "msg_5f0c2d6e9b1a4c7d8e3f2a1b0c9d8e7f",
"type": "alert.created",
"api_version": "2026-09-25",
"created_at": "2026-09-25T07:38:12Z",
"language": "en",
"data": {
"alert": {
"id": 184223,
"kind": "bs_capital_change",
"label": "Capital change",
"signal": "kapitaal",
"theme": "financieel",
"theme_label": "Finance",
"severity": "med",
"severity_label": "medium",
"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/en/c/voorbeeld-nv-0999999999"
},
"alerts_url": "https://checked.be/en/alerts"
}
}Fields
typealert.created(an alert),digest.created(the daily or weekly digest, if you tick it) orendpoint.test(the test button). Ignore types you do not know: more may be added.api_version- The version of this format. Fields may be added; existing fields do not change within a version.
language- The language of the labels and links (nl, fr or en), as set on the webhook. An alert's title and text are in Dutch, as in your account.
data.alert.kind- The kind of alert, a fixed code.
labelis its name,signalandthemethe row and theme of the alert rules (null for a summary of several deeds). data.alert.severitylow,medorhigh, withseverity_labelin the chosen language.data.company- The enterprise number (10 digits and in its usual notation), the name (null when unknown) and the link to the dossier on Checked.be.
A webhook only gets what the alert also shows in your account: no email address or other account data.
Verifying the signature
Checked signs following Standard Webhooks, so you can also use one of their libraries. By hand:
- Take the key: the part after whsec_, base64-decoded.
- Compute the HMAC-SHA256 of
{webhook-id}.{webhook-timestamp}.{body}, with the body exactly as received. - Compare
v1,plus its base64, in constant time, with each value in webhook-signature (space separated). - Reject a timestamp more than five minutes away from your clock.
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)));
}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(" "))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));
}Answers and retries
- Answer within 10 seconds with a 2xx status. Do heavy work afterwards.
- Any other answer, or none, is retried, later each time: after 1, 5, 15 and 30 minutes, then after 1 to 12 hours. A message is tried up to 6 times and expires after 3 days.
- Messages arrive in order: while the oldest is not delivered, the next ones wait. A Retry-After header is honoured (up to an hour).
- After 10 failures in a row the webhook is switched off and its owner gets an email. A 410 Gone answer switches it off without a retry.
- Redirects (3xx) are not followed. The address must use https and be public; addresses in an internal network are refused, even if the name points there later.
- Every attempt is in the webhook's delivery log, with the status and the duration.