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) :

  1. saisissez l'adresse de votre serveur (obligatoirement en https://, et accessible depuis Internet) ;
  2. copiez le secret de signature (whsec_...) dans votre configuration ;
  3. 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