# Den Callback empfangen und prüfen

Betreibe einen kleinen Empfänger in Node oder Python, prüfe die Signatur jedes Callbacks mit dem Signierungsgeheimnis deines Workspace, und lies die Antworten, die formbase pusht, wenn ein Request endet.

## Den Callback empfangen und prüfen

Ein Request mit einer callbackUrl endet mit einem signierten POST an diese URL: den Antworten, wenn die empfangende Person absendet, oder der Nachricht, dass er abgelaufen oder storniert wurde. Diese Anleitung betreibt einen Empfänger auf deinem Rechner, prüft die Signatur und gibt die Antworten aus.

<p>
  Du brauchst ein API-Token und ein veröffentlichtes Formular, wie in{' '}
  <a href="/de/guides/rest-api/send-a-request">Einen Request mit der REST API senden</a>, und Node oder Python auf deinem Rechner. Was ein
  Callback trägt und wie Wiederholungen funktionieren, steht in <a href="/de/requests/callbacks">Callbacks & Signierung</a>.
</p>

<h2 id="secret">1. Das Signierungsgeheimnis kopieren</h2>

<p>
  formbase signiert jeden Callback mit dem Request-Signierungsgeheimnis deines Workspace. Öffne <strong>OAuth und API-Schlüssel</strong> in
  der Workspace-Seitenleiste und finde die Karte <strong>Anfrage-Signierungsgeheimnis</strong>:
</p>

<p>
  Klicke auf den Kopieren-Button und bewahre das Geheimnis in einer Umgebungsvariable im Terminal auf, in dem dein Empfänger laufen wird:
</p>

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

> ⚠️ **Nicht neu generieren**
> <p>
>     Der dritte Button erzeugt ein neues Geheimnis, und das alte funktioniert sofort nicht mehr, auch für Callbacks, die schon unterwegs
>     sind. Du brauchst ihn nicht, um dieser Anleitung zu folgen.
>   </p>

<h2 id="receiver">2. Den Empfänger schreiben</h2>

<p>
  Speichere die Funktion <code>verify</code> aus <a href="/de/requests/callbacks#verify">Callbacks & Signierung</a> neben deinem Empfänger:
  als <code>verify.mjs</code> für Node, da es <code>import</code> nutzt, oder als <code>verify.py</code> für Python. Der Empfänger liest den
  rohen Body, prüft die Signatur und parst das JSON erst danach:
</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>
  Hashe den Body genau so, wie er angekommen ist. Ihn zu parsen und wieder in JSON zu verwandeln ändert die Bytes, und die Signatur passt
  nicht mehr.
</p>

<h2 id="tunnel">3. Ihn starten und ihm eine öffentliche URL geben</h2>

<p>Starte den Empfänger:</p>

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

<p>
  formbase ruft nur öffentliche HTTPS-Adressen auf, <code>localhost</code> reicht also nicht. Während du testest, gibt dir ein Tunnel deinem
  Rechner eine.{' '}
  <a href="https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/">
    Cloudflares Quick Tunnel
  </a>{' '}
  braucht kein Konto. Führe ihn in einem zweiten Terminal aus:
</p>

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

<p>
  Er gibt eine Adresse wie <code>https://horn-cod-classics-arab.trycloudflare.com</code> aus. ngrok und ähnliche Tools funktionieren
  genauso. In Produktion nutze stattdessen die eigene HTTPS-URL deines Servers.
</p>

<h2 id="request">4. Einen Request mit einer Callback-URL senden</h2>

<p>
  Erstelle eine Testanfrage wie in der <a href="/de/guides/rest-api/send-a-request#create">vorherigen Anleitung</a>, und füge{' '}
  <code>callbackUrl</code> mit der Tunnel-Adresse hinzu. Nutze deine eigene Formular-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>
  Nutze einen neuen <code>idempotencyKey</code>: Derselbe Schlüssel mit einem anderen Body wird abgelehnt. Öffne die <code>url</code> aus
  der Antwort, beantworte das Formular und sende ab.
</p>

<h2 id="see-it">5. Den Callback ankommen sehen</h2>

<p>
  Ein paar Sekunden nach dem Absenden gibt der Empfänger den Ereignistyp, deine externe ID und die Antworten aus. Der Node-Empfänger gibt
  aus:
</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>
  Um eine Ablehnung zu sehen, sende dem Empfänger selbst irgendeinen POST, zum Beispiel{' '}
  <code>curl -X POST -d '{}' http://localhost:8787</code>. Er hat keine gültige Signatur, also gibt der Empfänger{' '}
  <code>rejected: bad signature</code> aus und antwortet mit 401.
</p>

<h2 id="production">Bevor du live gehst</h2>

<ul>
  <li>
    <strong>Innerhalb von 10 Sekunden mit 2xx antworten.</strong> formbase markiert den Callback als zugestellt bei jedem 2xx. Ein 5xx, ein
    408, ein 429 oder ein Timeout wird etwa vier Stunden lang wiederholt; jeder andere 4xx, wie das 401 oben, stoppt die Wiederholungen.
    Siehe <a href="/de/requests/callbacks#retries">Wiederholungen</a>.
  </li>
  <li>
    <strong>
      Nach <code>type</code> verzweigen.
    </strong>{' '}
    Dieselbe URL hört auch auf <code>request.expired</code> und <code>request.canceled</code>, die keine Antworten tragen.
  </li>
  <li>
    <strong>Jedes Ereignis nur einmal behandeln.</strong> Eine Wiederholung trägt dieselbe <code>id</code>. Speichere die IDs, die du
    behandelt hast, und überspringe eine Wiederholung.
  </li>
  <li>
    <strong>
      <code>test</code> prüfen.
    </strong>{' '}
    Ein Callback von einer Testanfrage trägt <code>"test": true</code>. Lass <code>test</code> bei
    <code>requests.create</code> für echte empfangende Personen weg.
  </li>
</ul>

<p>
  War dein Empfänger länger nicht erreichbar, als die Wiederholungen dauern, sind die Antworten nicht verloren. Lies sie mit{' '}
  <code>requests.get</code>, oder sende den Callback erneut mit{' '}
  <a href="/de/developers/rest-api#requests-replay-callback">requests.replayCallback</a> oder von der{' '}
  <a href="/de/requests/managing-requests">Requests-Seite</a>.
</p>

<h2 id="next">Als Nächstes</h2>

<div class="not-prose grid gap-3 sm:grid-cols-2 mb-8">
  - [Callbacks & Signierung](/de/requests/callbacks) — Das vollständige Payload, die drei Ereignisse, und der Wiederholungsplan.
  - [API-Methoden](/de/developers/rest-api) — Jede Methode, mit ihren Parametern und Antworten.
</div>
