Webhooks et signature
Déclarez une ou plusieurs adresses dans le back-office (Webhooks), par mode. En production, l'adresse doit être en https. Chaque adresse a son secret whsec_….
Événements
session.document_processed | Fin de l'analyse de la pièce (pas d'événement quand l'usager doit seulement reprendre ses photos). |
session.completed | Décision prise : verified, rejected ou manual_review. Une revue manuelle terminée envoie un nouveau session.completed. |
session.expired | L'usager n'a pas terminé à temps. |
session.canceled | Session annulée. |
wallet.low_balance | Solde passé sous votre seuil d'alerte (production). |
test.ping | Envoyé par le bouton « Tester » du back-office. |
Les événements de session contiennent la session complète dans data.session, avec votre client_reference_id et vos metadata. En-têtes : CheckID-Event-Id, CheckID-Event-Type et CheckID-Signature.
Vérifier la signature
CheckID-Signature: t=<horodatage>,v1=<signature>, où la signature est le HMAC-SHA256, en hexadécimal, de t + "." + corps brut avec le secret de l'adresse. Refusez une signature fausse ou un horodatage de plus de 5 minutes.
import crypto from "node:crypto";
// Express : gardez le corps brut, la signature porte sur lui.
app.post("/webhooks/checkid", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("CheckID-Signature") ?? "";
const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
const expected = crypto
.createHmac("sha256", process.env.CHECKID_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
if (!fresh || !parts.v1 || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// Traitez event.type une seule fois par event.id, puis répondez vite.
res.sendStatus(200);
});
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_CHECKID_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $parts);
$expected = hash_hmac('sha256', $parts['t'] . '.' . $body, getenv('CHECKID_WEBHOOK_SECRET'));
if (abs(time() - (int) $parts['t']) > 300 || !hash_equals($expected, $parts['v1'] ?? '')) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
http_response_code(200);
import hashlib, hmac, os, time
def verify(header: str, body: bytes) -> bool:
parts = dict(item.split("=", 1) for item in header.split(",") if "=" in item)
if abs(time.time() - int(parts.get("t", 0))) > 300:
return False
expected = hmac.new(
os.environ["CHECKID_WEBHOOK_SECRET"].encode(),
f"{parts['t']}.".encode() + body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
Réponse et nouvelles tentatives
- Répondez
2xxen moins de 10 secondes ; traitez le reste en arrière-plan. - Sinon, CheckID réessaie après 1 min, 5 min, 30 min, 2 h, 6 h, 12 h et 24 h, puis abandonne et prévient vos développeurs par mail. Vous pouvez relancer une livraison depuis le back-office.
- Un même événement peut arriver plusieurs fois : dédoublonnez sur
id(ouCheckID-Event-Id). - Les redirections ne sont pas suivies.