Anleitungen · REST API
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.
Last checked
Du brauchst ein API-Token und ein veröffentlichtes Formular, wie in Einen Request mit der REST API senden, und Node oder Python auf deinem Rechner. Was ein Callback trägt und wie Wiederholungen funktionieren, steht in Callbacks & Signierung.
1. Das Signierungsgeheimnis kopieren
formbase signiert jeden Callback mit dem Request-Signierungsgeheimnis deines Workspace. Öffne OAuth und API-Schlüssel in der Workspace-Seitenleiste und finde die Karte Anfrage-Signierungsgeheimnis:

Klicke auf den Kopieren-Button und bewahre das Geheimnis in einer Umgebungsvariable im Terminal auf, in dem dein Empfänger laufen wird:
export FORMBASE_SIGNING_SECRET='rqs_...'Nicht neu generieren
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.
2. Den Empfänger schreiben
Speichere die Funktion verify aus Callbacks & Signierung neben deinem Empfänger:
als verify.mjs für Node, da es import nutzt, oder als verify.py für Python. Der Empfänger liest den
rohen Body, prüft die Signatur und parst das JSON erst danach:
import http from 'node:http'
import { verifyFormbaseCallback } from './verify.mjs'
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()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.
3. Ihn starten und ihm eine öffentliche URL geben
Starte den Empfänger:
node receiver.mjs # or: python3 receiver.pyformbase ruft nur öffentliche HTTPS-Adressen auf, localhost reicht also nicht. Während du testest, gibt dir ein Tunnel deinem
Rechner eine.
Cloudflares Quick Tunnel
braucht kein Konto. Führe ihn in einem zweiten Terminal aus:
cloudflared tunnel --url http://localhost:8787Er gibt eine Adresse wie https://horn-cod-classics-arab.trycloudflare.com aus. ngrok und ähnliche Tools funktionieren
genauso. In Produktion nutze stattdessen die eigene HTTPS-URL deines Servers.
4. Einen Request mit einer Callback-URL senden
Erstelle eine Testanfrage wie in der vorherigen Anleitung, und füge
callbackUrl mit der Tunnel-Adresse hinzu. Nutze deine eigene Formular-ID:
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
}
}'Nutze einen neuen idempotencyKey: Derselbe Schlüssel mit einem anderen Body wird abgelehnt. Öffne die url aus
der Antwort, beantworte das Formular und sende ab.
5. Den Callback ankommen sehen
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:
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"}Um eine Ablehnung zu sehen, sende dem Empfänger selbst irgendeinen POST, zum Beispiel
curl -X POST -d ‘’ http://localhost:8787. Er hat keine gültige Signatur, also gibt der Empfänger
rejected: bad signature aus und antwortet mit 401.
Bevor du live gehst
Innerhalb von 10 Sekunden mit 2xx antworten. 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 Wiederholungen.
Nach
typeverzweigen.Dieselbe URL hört auch auf
request.expiredundrequest.canceled, die keine Antworten tragen.Jedes Ereignis nur einmal behandeln. Eine Wiederholung trägt dieselbe
id. Speichere die IDs, die du behandelt hast, und überspringe eine Wiederholung.testprüfen.Ein Callback von einer Testanfrage trägt
“test”: true. Lasstestbeirequests.createfür echte empfangende Personen weg.
War dein Empfänger länger nicht erreichbar, als die Wiederholungen dauern, sind die Antworten nicht verloren. Lies sie mit
requests.get, oder sende den Callback erneut mit
requests.replayCallback oder von der
Requests-Seite.