Compte et intégrations
Recevoir et vérifier un Webhook signé
Quand une réponse est enregistrée, Kiosform prépare un POST JSON vers votre serveur. Une réponse synchronisée depuis une borne utilise le même mécanisme.
Mis à jour le
Avant de commencer
- Disposez d’un point de terminaison HTTPS public sur le port 443.
- Préparez un stockage de déduplication et conservez le secret uniquement côté serveur.
Les étapes
Ajouter le destinataire
Dans les réponses du formulaire, trouvez « Envois automatiques vers vos outils (webhooks) et historique ». Renseignez « URL de destination (HTTPS) » et une description, puis « Ajouter le webhook signé ». Votre point de terminaison apparaît avec son secret.
Vérifier la réception
Utilisez « Envoyer un test ». Le journal distingue l’attente, les tentatives et le résultat. Un état livré signifie que le destinataire a répondu avec un statut HTTP 2xx ; contrôlez aussi votre propre journal.
Vérifier la signature
Lisez le corps HTTP sous sa forme brute, avant tout décodage JSON. Vérifiez l’horodatage et le HMAC de « horodatage.corps_brut » avec le secret du Webhook. Utilisez l’un des exemples ci-dessous.
Enregistrer sans doublon
Utilisez « X-Kiosform-Delivery-Id » comme clé unique durable. Faites l’enregistrement de cette clé et de l’opération métier dans une même transaction. Une nouvelle réception du même identifiant ne doit pas répéter l’opération.

Corps et en-têtes
L’événement « response.created » contient directement form_id, form_version_id, workspace_id, response_id, source, device_id, client_created_at, submitted_at, deferred, idempotency_key et answers. Ces propriétés sont à la racine du JSON, pas dans un objet response. Le test « webhook.test » contient event, form_id et created_at ; ce n’est pas une réponse de participant.
X-Kiosform-Signature prend la forme t=horodatage_unix,v1=signature_hexadécimale. X-Kiosform-Delivery-Id reste stable pour une livraison, même lors d’une reprise. Le corps est figé ; l’horodatage et la signature sont renouvelés.
answers reprend les valeurs collectées et peut contenir des données personnelles, comme un nom ou un courriel. Choisissez un serveur destinataire autorisé, protégez son stockage et évitez de conserver le corps complet dans les journaux. Secrets et contenus reçus restent côté serveur, jamais dans un script de navigateur public.
{
"event": "response.created",
"form_id": "00000000-0000-4000-8000-000000000001",
"form_version_id": "00000000-0000-4000-8000-000000000002",
"workspace_id": "00000000-0000-4000-8000-000000000003",
"response_id": "00000000-0000-4000-8000-000000000004",
"source": "web",
"device_id": null,
"client_created_at": "2026-10-18T08:00:00.000Z",
"submitted_at": "2026-10-18T08:00:01.000Z",
"deferred": false,
"idempotency_key": "00000000-0000-4000-8000-000000000005",
"answers": {
"prenom": "Camille"
}
}Exemples de vérification
Ces fonctions attendent les octets du corps, le secret et l’en-tête exact. La tolérance est de cinq minutes, dans les deux sens : maintenez l’horloge du serveur à l’heure. Refusez la requête si la fonction retourne faux ; parsez le JSON et appliquez votre déduplication seulement après vérification.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody est un Buffer : ne pas reconstituer le JSON après décodage.
export function verifyKiosformWebhook(secret, rawBody, header, now = Math.floor(Date.now() / 1000)) {
if (!secret || !Buffer.isBuffer(rawBody) || typeof header !== 'string') return false;
const parts = /^t=([1-9][0-9]{0,11}),v1=([a-f0-9]{64})$/.exec(header);
if (!parts || parts[0] !== header || !Number.isFinite(now)) return false;
const timestamp = Number(parts[1]);
if (Math.abs(now - timestamp) > 300) return false;
const expected = createHmac('sha256', secret)
.update(parts[1] + '.', 'utf8').update(rawBody).digest();
const received = Buffer.from(parts[2], 'hex');
return timingSafeEqual(expected, received);
}Python 3
import hashlib
import hmac
import re
import time
def verify_kiosform_webhook(secret, raw_body, header, now=None):
"""raw_body contient les octets HTTP, avant le décodage JSON."""
if not secret or not isinstance(raw_body, bytes) or not isinstance(header, str):
return False
parts = re.fullmatch(r"t=([1-9][0-9]{0,11}),v1=([a-f0-9]{64})", header)
if parts is None:
return False
timestamp = int(parts[1])
if abs((time.time() if now is None else now) - timestamp) > 300:
return False
signed = (parts[1] + ".").encode("ascii") + raw_body
expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts[2])PHP
<?php
// $rawBody = file_get_contents('php://input'), avant json_decode.
function verifyKiosformWebhook(string $secret, string $rawBody, string $header, ?int $now = null): bool
{
if ($secret === '' || preg_match('/\At=([1-9][0-9]{0,11}),v1=([a-f0-9]{64})\z/', $header, $parts) !== 1) {
return false;
}
if (abs(($now ?? time()) - (int) $parts[1]) > 300) {
return false;
}
$expected = hash_hmac('sha256', $parts[1] . '.' . $rawBody, $secret);
return hash_equals($expected, $parts[2]);
}Reprises et erreurs
Quatre tentatives sont possibles : l’envoi initial, puis trois reprises éligibles après 1, 10 et 60 secondes. Le traitement en production passe toutes les dix secondes : ces délais ne promettent pas une réception à la seconde près.
Une erreur réseau ou un statut HTTP supérieur ou égal à 300 compte comme un échec. Les redirections ne sont pas suivies. Les cibles locales ou privées sont refusées. Après vingt échecs consécutifs, le point de terminaison est désactivé automatiquement ; une réussite 2xx remet le compteur à zéro.
La livraison est au moins une fois, pas exactement une fois. Un crash après votre réception peut entraîner un nouvel envoi. Répondez rapidement après avoir enregistré le travail de façon durable, puis traitez vos opérations longues dans votre propre file.
En cas de difficulté
Une livraison reste en attente
Vérifiez votre certificat HTTPS, le port 443, la disponibilité publique et le journal des tentatives. Comparez l’identifiant de livraison avec celui reçu par votre serveur.
La signature est refusée
Vérifiez le bon secret, l’horloge et la lecture du corps brut. Reconvertir le JSON en texte peut changer les espaces ou l’ordre des clés et invalider le HMAC.