# Recevoir et vérifier le callback

Exécutez un petit récepteur en Node ou Python, vérifiez la signature de chaque callback avec le secret de signature de votre espace de travail, et lisez les réponses que formbase envoie quand une demande se termine.

## Recevoir et vérifier le callback

Une demande avec une callbackUrl se termine par un POST signé vers cette URL : les réponses quand le destinataire soumet, ou l'annonce qu'elle a expiré ou a été annulée. Ce guide exécute un récepteur sur votre machine, vérifie la signature, et affiche les réponses.

<p>
  Il vous faut un token API et un formulaire publié, comme dans{' '}
  <a href="/fr/guides/rest-api/send-a-request">Envoyer une demande avec l'API REST</a>, et Node ou Python sur votre machine. Ce que porte un
  callback, et comment fonctionnent les nouvelles tentatives, est dans <a href="/fr/requests/callbacks">Callbacks et signature</a>.
</p>

<h2 id="secret">1. Copiez le secret de signature</h2>

<p>
  formbase signe chaque callback avec le secret de signature des demandes de votre espace de travail. Ouvrez{' '}
  <strong>OAuth et clés API</strong> dans la barre latérale de l'espace de travail et trouvez la carte{' '}
  <strong>Secret de signature des demandes</strong> :
</p>

<p>
  Cliquez sur le bouton de copie et gardez le secret dans une variable d'environnement dans le terminal où votre récepteur s'exécutera :
</p>

```
export FORMBASE_SIGNING_SECRET='rqs_...'
```

> ⚠️ **Ne le régénérez pas**
> <p>
>     Le troisième bouton crée un nouveau secret, et l'ancien cesse de fonctionner immédiatement, aussi pour les callbacks déjà en chemin.
>     Vous n'en avez pas besoin pour suivre ce guide.
>   </p>

<h2 id="receiver">2. Écrivez le récepteur</h2>

<p>
  Enregistrez la fonction <code>verify</code> depuis <a href="/fr/requests/callbacks#verify">Callbacks et signature</a> à côté de votre
  récepteur : sous <code>verify.mjs</code> pour Node, puisqu'il utilise <code>import</code>, ou sous <code>verify.py</code> pour Python. Le
  récepteur lit le corps brut, vérifie la signature, et ne parse le JSON qu'ensuite :
</p>

  
    
```
import http from 'node:http'
const secret = process.env.FORMBASE_SIGNING_SECRET
http
  .createServer((req, res) => {
    const chunks = []
    req.on('data', (chunk) => chunks.push(chunk))
    req.on('end', () => {
      const rawBody = Buffer.concat(chunks).toString('utf8')
      const signature = req.headers['x-formbase-signature'] ?? ''
      if (!verifyFormbaseCallback(rawBody, signature, secret)) {
        console.log('rejected: bad signature')
        res.writeHead(401).end()
        return
      }
      const event = JSON.parse(rawBody)
      console.log(event.type, event.data.request.externalId, JSON.stringify(event.data.answers))
      res.writeHead(200).end()
    })
  })
  .listen(8787)
```

  
  
    
```
import json, os
from http.server import BaseHTTPRequestHandler, HTTPServer
from verify import verify_formbase_callback
SECRET = os.environ['FORMBASE_SIGNING_SECRET']
class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        raw_body = self.rfile.read(int(self.headers['Content-Length']))
        signature = self.headers.get('X-formbase-Signature', '')
        if not verify_formbase_callback(raw_body, signature, SECRET):
            print('rejected: bad signature', flush=True)
            self.send_response(401)
            self.end_headers()
            return
        event = json.loads(raw_body)
        print(event['type'], event['data']['request'].get('externalId'), event['data'].get('answers'), flush=True)
        self.send_response(200)
        self.end_headers()
HTTPServer(('', 8787), Handler).serve_forever()
```

  

<p>
  Hachez le corps exactement comme il est arrivé. Le parser puis le retransformer en JSON change les octets, et la signature ne correspond
  plus.
</p>

<h2 id="tunnel">3. Exécutez-le et donnez-lui une URL publique</h2>

<p>Démarrez le récepteur :</p>

```
node receiver.mjs      # or: python3 receiver.py
```

<p>
  formbase n'appelle que des adresses HTTPS publiques, donc <code>localhost</code> ne suffira pas. Pendant que vous testez, un tunnel en
  donne une à votre machine.{' '}
  <a href="https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/">
    Le tunnel rapide de Cloudflare
  </a>{' '}
  ne nécessite aucun compte. Exécutez-le dans un second terminal :
</p>

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

<p>
  Il affiche une adresse comme <code>https://horn-cod-classics-arab.trycloudflare.com</code>. ngrok et les outils similaires fonctionnent de
  la même façon. En production, utilisez plutôt l'URL HTTPS propre à votre serveur.
</p>

<h2 id="request">4. Envoyez une demande avec une URL de callback</h2>

<p>
  Créez une demande de test comme dans le <a href="/fr/guides/rest-api/send-a-request#create">guide précédent</a>, et ajoutez{' '}
  <code>callbackUrl</code> avec l'adresse du tunnel. Utilisez votre propre ID de formulaire :
</p>

```
curl -X POST https://api.formbase.so/api/v1 \
  -H "Authorization: Bearer $FORMBASE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "requests.create",
    "params": {
      "formId": "jx75hdx8vb5hy1x85gm17nqgkn8f674g",
      "recipient": { "email": "ada@acme.example", "name": "Ada Lovelace" },
      "prefill": { "company_name": "Analytical Engines Ltd" },
      "readonly": ["company_name"],
      "externalId": "supplier-2044",
      "idempotencyKey": "supplier-2044",
      "callbackUrl": "https://horn-cod-classics-arab.trycloudflare.com/formbase",
      "test": true
    }
  }'
```

<p>
  Utilisez une nouvelle <code>idempotencyKey</code> : la même clé avec un corps différent est rejetée. Ouvrez <code>url</code> depuis la
  réponse, répondez au formulaire, et soumettez.
</p>

<h2 id="see-it">5. Voyez le callback arriver</h2>

<p>
  Quelques secondes après votre soumission, le récepteur affiche le type d'événement, votre ID externe et les réponses. Le récepteur Node
  affiche :
</p>

```
request.completed supplier-2044 {"certificate_of_incorporation":[{"name":"certificate-of-incorporation.pdf","size":635,"type":"application/pdf","url":"https://api.formbase.so/api/storage/...",...}],"company_name":"Analytical Engines Ltd","contact_email":"ada@acme.example","do_you_accept_our_30_day_payment_terms":"yes","vat_number":"GB123456789"}
```

<p>
  Pour voir un rejet, envoyez vous-même n'importe quel POST au récepteur, par exemple{' '}
  <code>curl -X POST -d '{}' http://localhost:8787</code>. Il n'a pas de signature valide, donc le récepteur affiche{' '}
  <code>rejected: bad signature</code> et répond 401.
</p>

<h2 id="production">Avant de passer en production</h2>

<ul>
  <li>
    <strong>Répondez 2xx en moins de 10 secondes.</strong> formbase marque le callback comme livré sur n'importe quel 2xx. Un 5xx, un 408,
    un 429 ou un délai dépassé est retenté pendant environ quatre heures ; tout autre 4xx, comme le 401 ci-dessus, arrête les tentatives.
    Voir <a href="/fr/requests/callbacks#retries">Nouvelles tentatives</a>.
  </li>
  <li>
    <strong>
      Bifurquez sur <code>type</code>.
    </strong>{' '}
    La même URL entend aussi <code>request.expired</code> et <code>request.canceled</code>, qui ne portent aucune réponse.
  </li>
  <li>
    <strong>Traitez chaque événement une fois.</strong> Une nouvelle tentative porte le même <code>id</code>. Stockez les ids déjà traités
    et ignorez les répétitions.
  </li>
  <li>
    <strong>
      Vérifiez <code>test</code>.
    </strong>{' '}
    Un callback venant d'une demande de test porte <code>"test": true</code>. Retirez <code>test</code> de <code>requests.create</code> pour
    les destinataires réels.
  </li>
</ul>

<p>
  Si votre récepteur était hors service plus longtemps que la durée des tentatives, les réponses ne sont pas perdues. Relisez-les avec{' '}
  <code>requests.get</code>, ou renvoyez le callback avec{' '}
  <a href="/fr/developers/rest-api#requests-replay-callback">requests.replayCallback</a> ou depuis la{' '}
  <a href="/fr/requests/managing-requests">page Demandes</a>.
</p>

<h2 id="next">Suite</h2>

<div class="not-prose grid gap-3 sm:grid-cols-2 mb-8">
  - [Callbacks et signature](/fr/requests/callbacks) — Le payload complet, les trois événements, et le planning des nouvelles tentatives.
  - [Méthodes API](/fr/developers/rest-api) — Chaque méthode, avec ses paramètres et ses réponses.
</div>
