# De callback ontvangen en verifiëren

Draai een kleine ontvanger in Node of Python, controleer de handtekening van elke callback met het ondertekeningsgeheim van je werkruimte, en lees de antwoorden die formbase pusht zodra een aanvraag eindigt.

## De callback ontvangen en verifiëren

Een aanvraag met een callbackUrl eindigt met een ondertekende POST naar die URL: de antwoorden zodra de ontvanger indient, of het bericht dat hij is verlopen of geannuleerd. Deze handleiding draait een ontvanger op je eigen machine, controleert de handtekening, en print de antwoorden.

<p>
  Je hebt een API-token en een gepubliceerd formulier nodig, zoals in{' '}
  <a href="/nl/guides/rest-api/send-a-request">Een aanvraag versturen met de REST API</a>, en Node of Python op je machine. Wat een callback
  draagt, en hoe nieuwe pogingen werken, staat in <a href="/nl/requests/callbacks">Callbacks & ondertekening</a>.
</p>

<h2 id="secret">1. Kopieer het ondertekeningsgeheim</h2>

<p>
  formbase ondertekent elke callback met het ondertekeningsgeheim voor aanvragen van je werkruimte. Open{' '}
  <strong>OAuth en API-sleutels</strong> in de zijbalk van de werkruimte en zoek de kaart{' '}
  <strong>Ondertekeningsgeheim voor aanvragen</strong>:
</p>

<p>Klik op de kopieerknop en bewaar het geheim in een omgevingsvariabele in de terminal waar je ontvanger zal draaien:</p>

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

> ⚠️ **Genereer het niet opnieuw**
> <p>
>     De derde knop maakt een nieuw geheim aan, en het oude stopt meteen met werken, ook voor callbacks die al onderweg zijn. Je hebt dit niet
>     nodig om deze handleiding te volgen.
>   </p>

<h2 id="receiver">2. Schrijf de ontvanger</h2>

<p>
  Bewaar de functie <code>verify</code> uit <a href="/nl/requests/callbacks#verify">Callbacks & ondertekening</a> naast je ontvanger: als{' '}
  <code>verify.mjs</code> voor Node, omdat het <code>import</code> gebruikt, of als <code>verify.py</code> voor Python. De ontvanger leest
  de ruwe body, controleert de handtekening, en parseert pas daarna de JSON:
</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>
  Hash de body precies zoals hij is binnengekomen. Hem parsen en terug omzetten naar JSON verandert de bytes, en de handtekening komt dan
  niet meer overeen.
</p>

<h2 id="tunnel">3. Draai hem en geef hem een publieke URL</h2>

<p>Start de ontvanger:</p>

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

<p>
  formbase roept alleen publieke HTTPS-adressen aan, dus <code>localhost</code> volstaat niet. Terwijl je test, geeft een tunnel je machine
  er een.{' '}
  <a href="https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/">
    Cloudflare's quick tunnel
  </a>{' '}
  heeft geen account nodig. Draai hem in een tweede terminal:
</p>

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

<p>
  Dat print een adres zoals <code>https://horn-cod-classics-arab.trycloudflare.com</code>. ngrok en vergelijkbare tools werken op dezelfde
  manier. Gebruik in productie de eigen HTTPS-URL van je server in plaats daarvan.
</p>

<h2 id="request">4. Verstuur een aanvraag met een callback-URL</h2>

<p>
  Maak een testaanvraag aan zoals in de <a href="/nl/guides/rest-api/send-a-request#create">vorige handleiding</a>, en voeg{' '}
  <code>callbackUrl</code> toe met het tunneladres. Gebruik je eigen formulier-id:
</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>
  Gebruik een nieuwe <code>idempotencyKey</code>: dezelfde sleutel met een andere body wordt geweigerd. Open de <code>url</code> uit het
  antwoord, beantwoord het formulier, en dien in.
</p>

<h2 id="see-it">5. Zie de callback binnenkomen</h2>

<p>Een paar seconden nadat je indient, print de ontvanger het event-type, jouw externe id en de antwoorden. De Node-ontvanger print:</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>
  Om een weigering te zien, stuur je de ontvanger zelf een willekeurige POST, bijvoorbeeld{' '}
  <code>curl -X POST -d '{}' http://localhost:8787</code>. Die heeft geen geldige handtekening, dus de ontvanger print{' '}
  <code>rejected: bad signature</code> en antwoordt met 401.
</p>

<h2 id="production">Voordat je live gaat</h2>

<ul>
  <li>
    <strong>Antwoord binnen 10 seconden met 2xx.</strong> formbase markeert de callback als afgeleverd bij elke 2xx. Een 5xx, een 408, een
    429 of een timeout wordt zo'n vier uur lang opnieuw geprobeerd; elke andere 4xx, zoals de 401 hierboven, stopt de nieuwe pogingen. Zie{' '}
    <a href="/nl/requests/callbacks#retries">Nieuwe pogingen</a>.
  </li>
  <li>
    <strong>
      Vertak op <code>type</code>.
    </strong>{' '}
    Dezelfde URL hoort ook <code>request.expired</code> en <code>request.canceled</code>, die geen antwoorden dragen.
  </li>
  <li>
    <strong>Verwerk elk event maar één keer.</strong> Een nieuwe poging draagt hetzelfde <code>id</code>. Bewaar de id's die je al hebt
    verwerkt en sla een herhaling over.
  </li>
  <li>
    <strong>
      Controleer <code>test</code>.
    </strong>{' '}
    Een callback van een testaanvraag heeft <code>"test": true</code>. Laat <code>test</code> weg bij <code>requests.create</code> voor
    echte ontvangers.
  </li>
</ul>

<p>
  Als je ontvanger langer offline was dan de nieuwe pogingen duren, zijn de antwoorden niet verloren. Lees ze met <code>requests.get</code>,
  of speel de callback opnieuw af met <a href="/nl/developers/rest-api#requests-replay-callback">requests.replayCallback</a> of vanaf de{' '}
  <a href="/nl/requests/managing-requests">pagina Aanvragen</a>.
</p>

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

<div class="not-prose grid gap-3 sm:grid-cols-2 mb-8">
  - [Callbacks & ondertekening](/nl/requests/callbacks) — De volledige payload, de drie events, en het schema voor nieuwe pogingen.
  - [API-methoden](/nl/developers/rest-api) — Elke methode, met zijn parameters en antwoorden.
</div>
