Adresse de l'API https://france-verification.com/api/v1
Webhooks
Plutôt que d'interroger l'API en boucle, donnez-nous une adresse : nous l'appelons dès qu'un envoi est terminé.
Configurer
Sur la page API de votre espace (france-verification.com/mon-api) :
- saisissez l'adresse de votre serveur (obligatoirement en
https://, et accessible depuis Internet) ; - copiez le secret de signature (
whsec_...) dans votre configuration ; - cliquez sur « Envoyer un webhook de test » : vous recevez un évènement
ping.
Une seule adresse par compte. Les envois de la clé test déclenchent aussi le webhook, ce qui permet de tout tester sans crédit.
Ce que nous envoyons
Une requête POST, corps JSON :
{
"id": "evt_9120",
"type": "submission.completed",
"created_at": "2026-09-28T14:49:17Z",
"data": {
"id": "sub_1842",
"mode": "live",
"status": "completed",
"...": "l'objet Submission complet, au format minimal"
}
}
| Évènement | Quand |
|---|---|
submission.completed |
Toutes les vérifications de l'envoi sont terminées (et le dossier éventuel aussi). data = l'objet Submission. |
ping |
Webhook de test, déclenché depuis la page API. |
En-têtes :
| En-tête | Contenu |
|---|---|
X-FV-Event |
Le type de l'évènement. |
X-FV-Delivery |
L'identifiant de l'évènement (evt_...), identique à id dans le corps. |
X-FV-Signature |
t=<horodatage unix>,v1=<signature> |
User-Agent |
FranceVerification-Webhooks/1.0 |
Répondre
Répondez un code 2xx en moins de 10 secondes. Faites le traitement lourd après avoir répondu (file d'attente de votre côté).
Sans réponse 2xx, nous réessayons 5 fois : après 1 minute, 5 minutes, 30 minutes, 2 heures, puis 6 heures. Ensuite l'évènement est abandonné, mais le résultat reste lisible par GET /submissions/{id}. Le même évènement peut donc arriver plus d'une fois : dédoublonnez sur id.
Vérifier la signature
La signature prouve que l'appel vient de nous. Calculez un HMAC SHA-256 de <t>.<corps brut> avec votre secret, et comparez-le à v1. Refusez aussi un horodatage vieux de plus de 5 minutes.
<?php
$secret = getenv('FV_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_FV_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', $sig['t'] . '.' . $body, $secret);
if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
http_response_code(200);
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(item.split("=", 1) for item in header.split(","))
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", "")) and abs(time.time() - int(parts["t"])) <= 300
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ""));
}
Important : signez le corps brut, tel que reçu, avant tout décodage JSON.
Changer de secret
« Régénérer le secret » sur la page API crée un nouveau secret : l'ancien ne signe plus rien dès cet instant. Mettez votre configuration à jour aussitôt.
Prêt à intégrer ? Créez votre clé test gratuitement : aucune analyse n'est facturée pendant le développement.
Créer ma clé API