Webhook
Tandia API puede notificar eventos de documentos y resúmenes hacia una URL configurada como webhook.
Configuración de webhook
La configuración pública documentada se realiza a nivel de compañía mediante el campo web_hook. El backend también puede usar un webhook configurado a nivel de cuenta como respaldo operativo.
Registrar webhook al crear compañía
POST /api/company/create
Host: invoice.test.tandia.io
Authorization: Bearer token-de-api
Content-Type: multipart/form-data
Campo dentro del form-data:
web_hook=https://cliente.com/webhooks/tandia
Actualizar webhook de una compañía existente
PUT /api/company/{taxId}
Host: invoice.test.tandia.io
Authorization: Bearer token-de-api
Content-Type: application/json
Payload:
{
"web_hook": "https://cliente.com/webhooks/tandia"
}
Validaciones
| Campo | Tipo | Requerido | Restricciones backend | Recomendación |
|---|---|---|---|---|
web_hook | string | No | máximo 255 caracteres | Usar URL HTTPS pública, estable e idempotente. |
Comportamiento
| Regla | Descripción |
|---|---|
| Prioridad | Si la compañía tiene web_hook, se envía a esa URL; si no tiene, el backend intenta usar el webhook de la cuenta. |
| Alcance | La actualización con PUT /api/company/{taxId} solo afecta a la compañía asociada a la cuenta autenticada. |
| Cuenta | La configuración directa del webhook de cuenta es operación interna y no se publica como endpoint de integración general. |
| URL vacía | Si no existe webhook de compañía ni de cuenta, no se envía notificación. |
Recepción de eventos
Cuando se genera un evento configurado y existe una URL de webhook, Tandia envía una solicitud HTTP POST a la URL registrada.
Eventos observados en backend
| Tipo | Eventos que disparan webhook |
|---|---|
| Documento | DocumentSucceeded, DocumentError |
| Resumen | SummarySucceeded, SummaryError, SummaryFailed |
Solicitud enviada
POST https://cliente.com/webhooks/tandia
Content-Type: application/json
Tandia-Signature: jwt-firmado
Payload: Tandia envía el modelo del documento o resumen mediante toArray(). La estructura exacta depende del tipo de evento y del recurso procesado.
Ejemplo simplificado:
{
"id": "doc_10987654321",
"status": "succeeded",
"company_id": "com_61a80925017e4",
"account_id": "acc_7449a64d83b17ba",
"metadata": {
"external_id": "pedido-123"
}
}
Entrega y reintentos
| Regla | Valor observado |
|---|---|
| Método | POST |
| Timeout | 30 segundos |
| Reintentos | 1 reintento ante ConnectionException |
| Espera entre reintentos | 2 segundos |
| Registro | El backend adjunta logs de éxito o error al documento/resumen. |
Verificación de firma
Cada webhook incluye la cabecera Tandia-Signature. Esta cabecera contiene un JWT firmado con el secret_key de la cuenta.
Tandia-Signature: jwt-firmado
El token de firma expira en 5 minutos y contiene al menos el id del documento o resumen enviado.
Proceso recomendado
- Lee la cabecera
Tandia-Signature. - Verifica el JWT con el
secret_keyde la cuenta. - Valida que el token no haya expirado.
- Compara el
idfirmado con elidrecibido en el payload. - Procesa el evento de forma idempotente.
Pseudocódigo:
signature = request.headers["Tandia-Signature"]
payload = verify_jwt(signature, secret_key)
if payload is valid and payload.id == request.body.id:
process_event(request.body)
else:
reject_request()
Seguridad del receptor
| Regla | Descripción |
|---|---|
| HTTPS | Publica el webhook únicamente por HTTPS. |
| Idempotencia | Un mismo evento puede recibirse más de una vez por reintentos o reprocesos. |
| Respuesta rápida | Responde 2xx luego de validar y encolar/procesar el evento. |
| Secretos | No expongas secret_key ni lo envíes en respuestas o logs. |
| Validación | No proceses eventos sin firma válida. |