# Webhooks personnalisés

Envoyez les données de soumission en POST vers n’importe quel endpoint HTTPS — votre backend, une fonction serverless, ou un proxy.

## Webhooks personnalisés

Envoyez chaque soumission complète sous forme de requête POST signée vers n’importe quel endpoint HTTPS — votre backend, une plateforme d’automatisation ou une fonction serverless.

<h2 id="how-it-works">Comment ça fonctionne</h2>

<p>
  À chaque soumission, Formstep envoie un corps JSON en POST vers votre URL webhook. La requête est signée avec HMAC-SHA256, relancée en cas
  d’échec, et consignée dans le journal d’événements de l’intégration. Cette page couvre la configuration. La forme exacte du payload, les
  en-têtes et l’algorithme de signature vivent dans la <a href="/fr/developers/webhooks-reference">référence webhook</a>.
</p>

<h2 id="add">Ajouter un webhook</h2>

> ℹ️ **Plusieurs webhooks**
> <p>Vous pouvez associer plusieurs webhooks à un seul formulaire. Chacun se déclenche indépendamment pour chaque événement.</p>

<h2 id="url-rules">Quelles URL Formstep accepte</h2>

<ul>
  <li>
    <code>https://</code> partout, ou <code>http://</code> pour <code>localhost</code> et <code>*.localhost</code> pendant le développement.
  </li>
  <li>Aucun identifiant dans l’URL, et au plus 2 048 caractères.</li>
  <li>
    Aucune adresse privée ou interne. Le nom d’hôte est résolu et revérifié juste avant <em>chaque</em> livraison, si bien qu’un
    enregistrement DNS repointé vers une adresse interne après la configuration reste refusé.
  </li>
</ul>

<p>Une URL refusée est un problème de configuration, pas transitoire : la livraison échoue définitivement au lieu d’être relancée.</p>

<h2 id="payload">Ce que vous recevez</h2>

<p>
  Chaque requête est la même enveloppe d’événement — <code>id</code>, <code>type</code>, <code>createdAt</code>, <code>apiVersion</code>,{' '}
  <code>test</code> et <code>data</code>. Dans <code>data</code> se trouvent le formulaire, la soumission (id, e-mail du répondant, heure de
  soumission, lien PDF, langue), un objet <code>answers</code> indexé par <a href="/fr/requests/field-keys">clé de champ</a>, et un objet{' '}
  <code>display</code> avec les mêmes clés en texte lisible. Chaque réponse apparaît une fois, dans chaque map.
</p>

<p>
  Consultez la référence pour la <a href="/fr/developers/webhooks-reference#payload">forme complète du payload</a>,{' '}
  <a href="/fr/developers/webhooks-reference#fields-vs-answers">answers et display</a>, et comment un{' '}
  <a href="/fr/building-forms/repeating-groups">groupe répétable</a> est représenté.
</p>

<p>
  Pour prévisualiser le corps exact pour votre formulaire, ouvrez l’intégration et développez <strong>Exemple de payload</strong> sous le
  secret de signature. Il affiche votre mapping actuel avec des réponses d’exemple.
</p>

<h2 id="signatures">Vérifier les signatures</h2>

<p>
  Chaque requête inclut un en-tête <code>X-Formstep-Signature</code> : <code>t=TIMESTAMP,sha256=HEX</code>, un HMAC-SHA256 de{' '}
  <code>TIMESTAMP.BODY</code> calculé avec votre secret de signature. Le secret lui-même n’est jamais envoyé. La référence contient un{' '}
  <a href="/fr/developers/webhooks-reference#signing">extrait de vérification prêt à copier</a>.
</p>

> ⚠️ **Vérifiez toujours en production**
> <p>
>     Sans vérification, toute personne qui découvre votre URL peut envoyer de fausses soumissions. Rejetez les requêtes dont la signature est
>     absente ou invalide.
>   </p>

<h2 id="abandoned-responses">Événements de réponses abandonnées</h2>

<p>
  Les webhooks personnalisés se déclenchent uniquement pour les soumissions complètes et les modifications — jamais pour les brouillons
  abandonnés. Un webhook personnalisé est le seul récepteur qui reçoit les deux : un abonnement Zapier, Make ou n8n choisit les premières
  soumissions ou les modifications, jamais les deux. Pour les brouillons abandonnés, utilisez un fournisseur qui dispose d’une étape{' '}
  <strong>Soumissions abandonnées</strong> : Google Sheets, Airtable, Notion, Slack, Discord, Linear ou GitHub Issues. Chacun a sa propre
  fenêtre d’inactivité et, le cas échéant, son propre modèle de message. Cette étape nécessite Pro ou Business.
</p>

<h2 id="retries">Nouvelles tentatives et échecs</h2>

<ul>
  <li>
    Une livraison réussit sur n’importe quel <code>2xx</code>.
  </li>
  <li>
    Jusqu’à 5 tentatives : la première est envoyée immédiatement, les suivantes attendent au moins 1, 2, 4 et 8 minutes. Formstep recherche
    les tentatives dues toutes les 30 minutes, la dernière tentative arrive donc environ deux heures après la première. Un en-tête{' '}
    <code>Retry-After</code> sur un <code>429</code> ou <code>5xx</code> est respecté quand il demande une attente plus longue.
  </li>
  <li>
    <code>429</code>, <code>5xx</code>, les délais dépassés et les échecs de connexion sont relancés. Toute autre erreur <code>4xx</code>{' '}
    échoue immédiatement.
  </li>
  <li>
    Après 5 échecs consécutifs, l’intégration se met en pause automatiquement et la personne qui l’a configurée reçoit un e-mail. Corrigez
    l’endpoint, puis appuyez sur <strong>Reprendre</strong>.
  </li>
  <li>
    <code>401</code>, <code>403</code> et <code>404</code> arrêtent immédiatement l’intégration avec un statut d’erreur et le même e-mail —
    inutile d’attendre cinq échecs.
  </li>
  <li>
    Les livraisons qui ont épuisé leurs 5 tentatives s’accumulent dans une bannière sur l’intégration. <strong>Réessayer tous</strong> les
    remet en file et réactive une intégration en pause.
  </li>
</ul>

<h2 id="testing">Tests</h2>

<p>
  <strong>Envoyer un événement de test</strong> apparaît sur l’étape Finaliser et à nouveau sur l’intégration enregistrée. Il envoie en POST
  un exemple de soumission synthétisé — <code>"John Doe"</code> pour le texte, <code>42</code> pour les nombres,{' '}
  <code>john@example.com</code> pour l’e-mail — signé et avec vos en-têtes personnalisés, exactement comme une vraie livraison. Depuis
  l’intégration enregistrée, il inscrit aussi une entrée de test de connexion dans le journal d’événements.
</p>

<p>Pour le développement local, exposez votre serveur de développement avec un tunnel :</p>

```
# ngrok
ngrok http 3000

# cloudflare tunnel

cloudflared tunnel --url http://localhost:3000
```

<h2 id="faq">FAQ</h2>

  <p>
    Oui. Chacun possède sa propre URL, son secret de signature et ses en-têtes personnalisés. Tous les webhooks actifs se déclenchent
    indépendamment pour chaque soumission.
  </p>

  <p>
    Oui — jusqu’à 5, ajoutés lors de la configuration ou plus tard depuis l’intégration. <code>Content-Type</code> est défini
    automatiquement et toute tentative de le remplacer est ignorée.
  </p>

  <p>
    Non. Le secret de signature est généré une seule fois à la création du webhook et ne peut pas être modifié. Si vous avez besoin d’un
    nouveau secret, supprimez le webhook et créez-en un nouveau.
  </p>

  <p>Oui. Ouvrez l’intégration, modifiez l’URL, et enregistrez. Le secret de signature et l’historique des événements restent associés.</p>

  <p>
    Uniquement pour <code>localhost</code> et <code>*.localhost</code> en développement. Toutes les autres URL doivent utiliser HTTPS.
  </p>

  <p>
    Supprimez-la depuis Paramètres du formulaire → Intégrations. Formstep cesse d’envoyer des requêtes immédiatement, et l’historique des
    événements de l’intégration est supprimé avec elle.
  </p>

<h2 id="next-steps">Prochaines étapes</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Référence webhook](/fr/developers/webhooks-reference) — Payload, en-têtes et vérification de signature
  - [Airtable](/fr/integrations/airtable) — Envoyez les soumissions dans une base Airtable
  - [Linear](/fr/integrations/linear) — Transformez les soumissions en tickets Linear
  - [Groupes répétables](/fr/building-forms/repeating-groups) — Laissez les répondants ajouter autant d'entrées qu'ils en ont besoin
</div>
