Guías · API REST
Recibe y verifica el callback
Una solicitud con callbackUrl termina con un POST firmado a esa URL: las respuestas cuando el destinatario la envía, o el aviso de que expiró o se canceló. Esta guía ejecuta un receptor en tu máquina, comprueba la firma, e imprime las respuestas.
Last checked
Necesitas un token de API y un formulario publicado, como en Envía una solicitud con la API REST, y Node o Python en tu máquina. Qué lleva un callback, y cómo funcionan los reintentos, está en Callbacks y firma.
1. Copia el secreto de firma
formbase firma cada callback con el secreto de firma de solicitudes de tu espacio de trabajo. Abre OAuth y claves de API en la barra lateral del espacio de trabajo y busca la tarjeta Secreto de firma de solicitudes:

Haz clic en el botón de copiar y guarda el secreto en una variable de entorno en la terminal donde se ejecutará tu receptor:
export FORMBASE_SIGNING_SECRET='rqs_...'No lo regeneres
El tercer botón crea un secreto nuevo, y el antiguo deja de funcionar de inmediato, también para los callbacks que ya están en camino. No lo necesitas para seguir esta guía.
2. Escribe el receptor
Guarda la función verify de Callbacks y firma junto a tu receptor: como
verify.mjs para Node, ya que usa import, o como verify.py para Python. El receptor lee el cuerpo en
bruto, comprueba la firma, y solo entonces analiza el JSON:
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()Calcula el hash del cuerpo exactamente como llegó. Analizarlo y volver a convertirlo en JSON cambia los bytes, y la firma deja de coincidir.
3. Ejecútalo y dale una URL pública
Arranca el receptor:
node receiver.mjs # or: python3 receiver.pyformbase solo llama a direcciones HTTPS públicas, así que localhost no sirve. Mientras pruebas, un túnel le da una a tu
máquina.
El túnel rápido de Cloudflare
no necesita cuenta. Ejecútalo en una segunda terminal:
cloudflared tunnel --url http://localhost:8787Imprime una dirección como https://horn-cod-classics-arab.trycloudflare.com. ngrok y herramientas similares funcionan igual.
En producción, usa la propia URL HTTPS de tu servidor en su lugar.
4. Envía una solicitud con una URL de callback
Crea una solicitud de prueba como en la guía anterior, y añade
callbackUrl con la dirección del túnel. Usa tu propio ID de formulario:
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
}
}'Usa una idempotencyKey nueva: la misma clave con un cuerpo diferente se rechaza. Abre el url de la respuesta,
responde el formulario, y envíalo.
5. Mira llegar el callback
Unos segundos después de enviar, el receptor imprime el tipo de evento, tu ID externo y las respuestas. El receptor de Node imprime:
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"}Para ver un rechazo, envía tú mismo cualquier POST al receptor, por ejemplo curl -X POST -d ‘’ http://localhost:8787. No
tiene una firma válida, así que el receptor imprime rejected: bad signature y responde 401.
Antes de pasar a producción
Responde 2xx en 10 segundos. formbase marca el callback como entregado con cualquier 2xx. Un 5xx, un 408, un 429 o un tiempo de espera agotado se reintenta durante unas cuatro horas; cualquier otro 4xx, como el 401 de arriba, detiene los reintentos. Consulta Reintentos.
Ramifica según
type.La misma URL también recibe
request.expiredyrequest.canceled, que no llevan respuestas.Maneja cada evento una sola vez. Un reintento lleva el mismo
id. Guarda los ids que ya manejaste y salta una repetición.Comprueba
test.Un callback de una solicitud de prueba lleva
“test”: true. Quitatestderequests.createpara destinatarios reales.
Si tu receptor estuvo caído más tiempo del que duran los reintentos, las respuestas no se pierden. Léelas con requests.get, o
envía el callback de nuevo con requests.replayCallback o desde la
página de Solicitudes.