Menu de la documentation

Webhooks de consentement : des événements signés vers votre serveur

Recevez les événements consent.recorded et consent.withdrawn sur votre serveur, vérifiez la signature HMAC avec le SDK et testez la livraison signée.

Voir en Markdown
Mis à jour le

Les webhooks de consentement poussent chaque décision vers votre serveur sous forme de POST HTTP signé : consent.recorded quand un visiteur accorde au moins une catégorie, consent.withdrawn quand il refuse ou se rétracte. C'est ce qui rend le droit de retrait applicable côté serveur — quand un visiteur se rétracte, votre backend l'apprend et peut purger ou cesser le traitement.

Créer un webhook

Deux chemins, même contrat :

  • Dans l'app — l'assistant de configuration sur la page Intégration du Builder : saisissez l'URL de votre endpoint, avec la possibilité de limiter le webhook à une seule bannière (par défaut : tout le workspace).
  • Via MCP — l'outil configure_consent_webhook, si vous pilotez FlowConsent depuis Claude ou un autre client MCP.

Dans les deux cas, vous recevez un secret de signature au format whsec_….

La signature

Chaque livraison porte un en-tête X-FlowConsent-Signature, style Stripe :

X-FlowConsent-Signature: t=1722326400,v1=5f8a1c…

t est un horodatage Unix et v1 = HMAC-SHA256(secret, "<t>.<corps brut>"). L'horodatage borne le rejeu : rejetez les livraisons dont le t est trop ancien.

Vérifier avec le SDK serveur

@flowconsent/server-sdk fournit verifyWebhookSignature — comparaison en temps constant, tolérance de rejeu de 300 s par défaut :

app/api/flowconsent-webhook/route.ts
ts
import { verifyWebhookSignature } from '@flowconsent/server-sdk'
 
export async function POST(request: Request) {
  const rawBody = await request.text() // corps brut, avant tout JSON.parse
  const check = await verifyWebhookSignature(
    rawBody,
    request.headers.get('X-FlowConsent-Signature'),
    process.env.FLOWCONSENT_WEBHOOK_SECRET!,
  )
  if (!check.valid) {
    // check.reason : 'missing_header', 'malformed_header',
    // 'timestamp_out_of_tolerance', 'signature_mismatch'
    return new Response('invalid signature', { status: 400 })
  }
 
  const event = JSON.parse(rawBody)
  if (event.test) return new Response('ok') // événement de test du wizard — jamais un vrai consentement
 
  if (event.type === 'consent.withdrawn') {
    // arrêter les traitements pour event.visitor_id, purger les systèmes aval…
  }
  return new Response('ok')
}

Les événements

consent.recorded — format exact de livraison
json
{
  "id": "5f0e0a1c-…",
  "type": "consent.recorded",
  "banner": "FC-A1B2C3",
  "visitor_id": "v_8f3d…",
  "action": "accept_all",
  "categories": {
    "functional": true,
    "analytics": true,
    "marketing": false,
    "preferences": false
  },
  "services": { "google-analytics": true },
  "policy_version": "3",
  "occurred_at": "2026-07-30T09:41:22.000Z"
}
  • type vaut consent.recorded quand au moins une des catégories analytics, marketing, preferences est accordée ; sinon consent.withdrawn.
  • banner est le code licence public de la bannière — les identifiants internes ne sont jamais exposés.
  • visitor_id est l'identifiant pseudonyme que l'on retrouve dans les journaux de consentement et dans le decision.visitorId du SDK serveur.
  • occurred_at est en ISO 8601.

Tester la livraison

Le « test d'envoi » de l'assistant envoie un événement consent.test à votre endpoint, signé avec votre vrai secret et portant un marqueur explicite "test": true — ne le traitez jamais comme un consentement réel. Il confirme, de bout en bout, que votre endpoint reçoit, vérifie et répond. Le résultat de livraison (statut HTTP, durée) s'affiche dans l'app.

Livraison et réessais

  • 3 tentatives par livraison, backoff exponentiel (250 ms, 500 ms, 1 s entre les tentatives), timeout de 5 s chacune.
  • Une réponse 4xx (hors 429) arrête les réessais — réessayer n'y changerait rien.
  • Répondez 2xx vite et traitez en asynchrone : tout le reste compte comme une livraison échouée.
  • Le statut et l'heure de la dernière livraison sont visibles sur chaque webhook dans l'app.