Saltar al contenido principal

Webhooks y verificación de firma

Los webhooks de Frihet permiten recibir notificaciones HTTP en tiempo real cuando ocurren eventos en tu cuenta. En lugar de hacer polling a la API, configura una URL y Frihet enviará un POST con los datos del evento en el momento en que se produzca.

Recorrido rápido: de tu primera lectura API a un webhook verificado​

Si ya completaste tu primera lectura autenticada con GET /v1/clients?limit=5 en Desarrolladores: primeros pasos, este recorrido valida el siguiente hito de integración: comprobar permisos y plan, verificar criptográficamente la firma X-Frihet-Signature (HMAC-SHA256) sobre el cuerpo crudo (rawBody) antes de abrir tráfico externo y confirmar la entrega desde el panel o vía API REST.

1. Requisitos previos, permisos y límite de plan​

Antes de registrar un endpoint en POST /v1/webhooks o desde Integraciones → Webhooks, verifica estos cuatro requisitos de contrato:

RequisitoContrato en producciónDónde comprobarlo
Lectura API previaHaber recibido 200 OK en GET https://api.frihet.io/v1/clients?limit=5 con tu cabecera X-API-Key: fri_...Desarrolladores: primeros pasos
Permisos de la claveLas API keys actuales tienen acceso completo a los recursos de la cuenta (read y write sobre /v1/*). No existen scopes granulares por recurso en v1API Keys (Integraciones → API Keys)
Plan requerido y cupoLos webhooks requieren el plan Premium (o Enterprise). Límites por cuenta: máximo 20 webhooks registrados y 100 KB por payload entregadoAjustes → Plan y Facturación y Planes y facturación
URL y secreto (secret)url pública con https:// (http://localhost o 127.0.0.1 solo en desarrollo local; IPs privadas RFC 1918 bloqueadas contra SSRF). El campo secret (máximo 256 caracteres) es solo de escritura en POST /v1/webhooks y PATCH /v1/webhooks/:id: las lecturas devuelven hasSecret: true sin exponer el texto planoConfiguración de webhooks

2. Recorrido determinista: verificar firma HMAC-SHA256 en local​

El fallo más frecuente al integrar webhooks es calcular el HMAC sobre un objeto JSON ya parseado y re-serializado por el framework web, lo que altera espacios o claves y rompe la firma. El flujo completo de firma, verificación en tiempo constante y deduplicación por X-Frihet-Delivery-Id sigue esta secuencia:

Flujo de verificación: Evento webhook → Firma HMAC → Validación → Rechazo o aceptación

X-Frihet-Signature (HMAC-SHA256) + X-Frihet-Delivery-Id
Emisión del evento
invoice.paidPOST HTTPS

Frihet serializa el evento (máx. 100 KB) y genera un identificador único de entrega X-Frihet-Delivery-Id.

Firma sobre rawBody
HMAC-SHA256X-Frihet-Signature

Con el secret del endpoint, Frihet calcula sha256=<hex> exactamente sobre los bytes crudos UTF-8 del cuerpo HTTP.

Validación en receptor
timingSafeEqualDeduplicación

Tu servidor lee el rawBody antes de parsear JSON, recalcula el HMAC-SHA256 y comprueba si X-Frihet-Delivery-Id ya fue procesado.

Decisión HTTP (< 30 s)
401 Rechazo200 Aceptación

Si la firma falla responde 401; si es válida responde 200 OK rápido y ejecuta la lógica de negocio una sola vez.

Rechazo (401 Unauthorized) — Firma inválida o JSON re-serializado

El digest calculado no coincide con X-Frihet-Signature. El receptor descarta el payload sin tocar base de datos; Frihet registra el fallo y aplica hasta 3 reintentos con backoff exponencial.

Aceptación (200 OK) — Firma válida + control de idempotencia

La firma coincide en tiempo constante. Si X-Frihet-Delivery-Id es nuevo, se guarda y se procesa el evento; si ya existía por un reintento de red, se devuelve 200 OK sin duplicar efectos.

Ver especificación textual y fuente Mermaid de la secuencia de validación
sequenceDiagram
  participant F as Frihet ERP
  participant R as Receptor HTTPS (rawBody)
  F->>R: POST payload + X-Frihet-Signature (sha256=...) + X-Frihet-Delivery-Id
  R->>R: Calcular HMAC-SHA256(secret, rawBody) y comparar con timingSafeEqual
  alt Firma inválida o cuerpo alterado
    R-->>F: 401 Unauthorized (Rechazar entrega, desencadena reintento con backoff)
  else Firma válida y X-Frihet-Delivery-Id ya procesado
    R-->>F: 200 OK idempotente (Ignorar duplicado sin reprocesar negocio)
  else Firma válida y X-Frihet-Delivery-Id nuevo
    R-->>F: 200 OK (< 30s) + registrar X-Frihet-Delivery-Id + encolar trabajo
  end

Puedes comprobar en vivo los tres escenarios (firma válida 200 OK, cuerpo alterado 401 Unauthorized e identificador de entrega repetido 200 OK idempotente) directamente en el navegador con el simulador local determinista, o ejecutar el comando de Node.js en tu terminal:

Laboratorio local de verificación de firma e idempotencia

100 % local · Web Crypto HMAC-SHA256 · Sin red

Prueba los tres casos sobre el fixture sintético de Estudio Norte S.L. (F-2026-0001) o edita el rawBody para recalcular el HMAC-SHA256 con TextEncoder + Web Crypto. La lista en memoria simula la deduplicación por X-Frihet-Delivery-Id (no sustituye persistencia duradera ni control de concurrencia en tu base de datos).

HTTP 200 OK — Firma válida y entrega nueva aceptadaIDs en memoria (simulación local de deduplicación): [del_01J8X9A1B0]

El digest HMAC-SHA256 calculado con Web Crypto sobre los bytes UTF-8 del rawBody coincide con X-Frihet-Signature y X-Frihet-Delivery-Id no figura en la lista en memoria.

  • HMAC-SHA256 calculado con Web Crypto sobre rawBody (esperado):sha256=970173bb5ff9661383456c2737b482c7f654992ec27204d88fa87bf9fc53f10a
  • Cabecera X-Frihet-Signature verificada con crypto.subtle.verify (en tu servidor Node usa crypto.timingSafeEqual):sha256=970173bb5ff9661383456c2737b482c7f654992ec27204d88fa87bf9fc53f10a
  • Efecto simulado en el receptor:Registrar del_01J8X9N8Q4 y procesar el cobro de F-2026-0001.

Antes de exponer tu receptor HTTPS en internet, ejecuta también esta prueba determinista en local con Node.js (crypto nativo, sin dependencias externas). Utiliza el fixture sintético de documentación (Estudio Norte S.L., NIF B12345674, facturacion@example.com, factura F-2026-0001) y el secreto de ejemplo whsec_test_ejemplo_doc_no_usar_en_produccion:

node -e '
const crypto = require("crypto");
const secret = "whsec_test_ejemplo_doc_no_usar_en_produccion";
const rawBody = "{\"event\":\"invoice.paid\",\"timestamp\":\"2026-10-15T10:00:00.000Z\",\"data\":{\"id\":\"inv_01J8X9N4P2\",\"invoiceNumber\":\"F-2026-0001\",\"clientId\":\"cli_01J8X9M2K4\",\"clientName\":\"Estudio Norte S.L.\",\"clientTaxId\":\"B12345674\",\"clientEmail\":\"facturacion@example.com\",\"subtotal\":1500,\"taxAmount\":315,\"total\":1815,\"status\":\"paid\"}}";
const headerSig = "sha256=970173bb5ff9661383456c2737b482c7f654992ec27204d88fa87bf9fc53f10a";

const expectedHex = crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
const receivedHex = headerSig.replace(/^sha256=/, "");
const isValid =
expectedHex.length === receivedHex.length &&
crypto.timingSafeEqual(Buffer.from(expectedHex, "hex"), Buffer.from(receivedHex, "hex"));

console.log(JSON.stringify({ isValid, event: "invoice.paid", signaturePrefix: headerSig.slice(0, 23) + "..." }));
'
Datos sintéticos y frontera de verificación
  • Por qué B12345674 y example.com: B12345674 supera la validación algorítmica del dígito de control español (4), pero no acredita existencia, inexistencia ni titularidad en el censo de la AEAT; y example.com es un dominio reservado para documentación por IANA (RFC 6761), no un buzón controlado de QA ni una garantía de bloqueo de envío. No uses estos datos para emitir o enviar facturas reales.
  • Qué prueba este comando y qué requiere red real: este comando verifica de forma determinista tu lógica de firma (createHmac sobre rawBody + comparación en tiempo constante timingSafeEqual). Para comprobar la conectividad de red, TLS y cortafuegos desde Google Cloud (us-central1), registra tu URL en Integraciones → Webhooks y pulsa Enviar prueba (que entrega un payload webhook.test).

3. Resultado esperado: verificación local y entrega de prueba​

  1. En tu terminal local (comando anterior): obtienes exactamente esta salida con código de salida 0:
{ "isValid": true, "event": "invoice.paid", "signaturePrefix": "sha256=970173bb5ff96613..." }
  1. Al registrar el webhook (POST /v1/webhooks): la API responde 201 Created con el identificador del webhook (id), active: true, hasSecret: true y la lista de los tipos de evento suscritos (dentro del catálogo de 15 tipos de evento).
  2. Al pulsar Enviar prueba en el panel (Integraciones → Webhooks): tu servidor recibe una petición POST con las cabeceras X-Frihet-Event: webhook.test, X-Frihet-Delivery-Id, X-Frihet-Timestamp y X-Frihet-Signature. Si tu endpoint responde 200 OK en menos de 30 segundos, la pestaña Entregas registra el intento como delivered.

Puedes registrar el endpoint y disparar la entrega de prueba desde cualquiera de los tres canales equivalentes:

  1. Ve a Integraciones → Webhooks y pulsa Crear webhook.
  2. Introduce tu URL HTTPS (https://...), selecciona invoice.paid y define tu secret de verificación.
  3. Abre el webhook recién creado, pulsa Enviar prueba (webhook.test) y comprueba en la pestaña Entregas que aparece como delivered (200 OK).

4. Diagnóstico y recuperación de webhooks​

Si el registro del webhook o la verificación de entregas falla, aplica esta tabla de recuperación directa:

Síntoma o códigoCausa raíz verificableAcción de recuperación
isValid: false o 401 en tu receptorTu servidor aplicó JSON.parse antes de calcular el HMAC o usó un secret distinto al configuradoPasa los bytes crudos (express.raw({ type: 'application/json' }) en Express o request.data en Flask) a createHmac. Si perdiste el secreto, rótalo con PATCH /v1/webhooks/:id enviando un nuevo campo secret
403 Forbidden al llamar a POST /v1/webhooksLa cuenta no está en el plan Premium o ya alcanzó el tope de 20 webhooks activos/inactivosRevisa tu suscripción en Ajustes → Plan y Facturación o lista tus registros actuales con GET /v1/webhooks y elimina los obsoletos con DELETE /v1/webhooks/:id
400 Bad Request al guardar la urlLa URL usa http:// fuera de localhost/127.0.0.1, resuelve a una IP privada (protección SSRF) o incluye tipos de evento fuera de los 15 tipos de evento soportadosUsa un dominio público con certificado TLS válido (https://) y comprueba los nombres exactos en Tipos de evento
Estado paused (pausedReason: "consecutive_failures")El circuit breaker pausó el webhook tras 3 entregas fallidas consecutivas (cada una con hasta 3 intentos y timeout de 30 s)Corrige el error 5xx/timeout en tu servidor, verifica que responde 2xx en menos de 30 s y reactívalo desde Integraciones → Webhooks o enviando PATCH /v1/webhooks/:id con {"active": true}

5. Siguiente acción​


Configuración​

Puedes gestionar los webhooks desde el panel de Frihet (Integraciones → Webhooks) o de forma programática a través de la API REST.

Desde el panel​

  1. Ve a Integraciones → Webhooks en tu cuenta de Frihet
  2. Pulsa Crear webhook
  3. Introduce un nombre descriptivo, la URL de destino y selecciona los eventos que quieres recibir
  4. Opcionalmente, define un secreto para la verificación HMAC (recomendado)
  5. Guarda el webhook

API REST de webhooks​

Gestiona webhooks de forma programática con los endpoints CRUD completos.

Listar webhooks​

GET /v1/webhooks
curl https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui"

Respuesta (200):

{
"data": [
{
"id": "wh_abc123",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "invoice.created"],
"active": true,
"createdAt": "2026-02-01T10:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}

Obtener webhook​

GET /v1/webhooks/:id
curl https://api.frihet.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: fri_tu-clave-aqui"

Crear webhook​

POST /v1/webhooks
CampoTipoRequeridoDescripción
urlstringSiURL de destino (HTTPS obligatorio en producción)
eventsstring[]SiLista de tipos de evento a recibir
secretstringNoSecreto para firma HMAC-SHA256 (recomendado)
activebooleanNoEstado del webhook (defecto: true)
curl -X POST https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"secret": "mi-secreto-hmac"
}'

Respuesta (201):

{
"id": "wh_def456",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"active": true,
"createdAt": "2026-03-18T10:00:00.000Z"
}

Actualizar webhook​

PATCH /v1/webhooks/:id

Solo necesitas enviar los campos que quieras modificar.

curl -X PATCH https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"events": ["invoice.paid", "invoice.created", "expense.created"],
"active": false
}'

Respuesta (200): Objeto de webhook actualizado.

Eliminar webhook​

DELETE /v1/webhooks/:id
curl -X DELETE https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui"

Respuesta: 204 No Content


Requisitos de la URL:

  • Debe usar HTTPS (se permite HTTP solo para localhost y 127.0.0.1 durante el desarrollo)
  • No se permiten IPs privadas (10.x, 172.16-31.x, 192.168.x) para evitar ataques SSRF
  • Debe responder con un código 2xx en menos de 30 segundos

Límites:

  • Máximo 20 webhooks por cuenta
  • Payload máximo de 100 KB por entrega

Tipos de evento​

Frihet emite 15 tipos de evento, agrupados por recurso.

Facturas (4 eventos)​

EventoDescripción
invoice.createdSe ha creado una nueva factura
invoice.updatedSe ha modificado una factura existente
invoice.paidUna factura ha sido marcada como pagada (completa)
invoice.overdueUna factura ha superado su fecha de vencimiento

Pagos (1 evento)​

EventoDescripción
payment.succeededUn pago se ha completado correctamente (Stripe, portal de cliente)

Presupuestos (4 eventos)​

EventoDescripción
quote.createdSe ha creado un nuevo presupuesto
quote.updatedSe ha modificado un presupuesto existente
quote.acceptedUn cliente ha aceptado un presupuesto (cambio de estado a accepted)
quote.rejectedUn cliente ha rechazado un presupuesto (cambio de estado a rejected)

Gastos (2 eventos)​

EventoDescripción
expense.createdSe ha registrado un nuevo gasto
expense.updatedSe ha modificado un gasto existente

Clientes (2 eventos)​

EventoDescripción
client.createdSe ha dado de alta un nuevo cliente
client.updatedSe han modificado los datos de un cliente

Productos (2 eventos)​

EventoDescripción
product.createdSe ha creado un nuevo producto o servicio
product.updatedSe ha modificado un producto o servicio existente

Estructura del payload​

Cada entrega de webhook es una petición POST con cuerpo JSON. La estructura es la misma para todos los tipos de evento:

{
"event": "invoice.paid",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"id": "inv_abc123",
"clientName": "Acme S.L.",
"items": [{ "description": "Consultoria", "quantity": 10, "unitPrice": 75 }],
"total": 750,
"status": "paid",
"paidAt": "2026-02-12T14:29:58.000Z",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-12T14:30:00.000Z"
}
}

El campo data contiene el estado completo del recurso en el momento del evento.


Cabeceras de entrega​

Cada petición de webhook incluye las siguientes cabeceras:

CabeceraDescripciónEjemplo
Content-TypeTipo de contenidoapplication/json
X-Frihet-EventTipo de eventoinvoice.paid
X-Frihet-Delivery-IdIdentificador único de la entregad4e5f6a7b8c9
X-Frihet-TimestampMarca de tiempo ISO 86012026-02-12T14:30:00.000Z
X-Frihet-SignatureFirma HMAC-SHA256 del payloadsha256=a1b2c3d4e5f6...

La cabecera X-Frihet-Signature solo se incluye si has configurado un secreto en el webhook.


Verificación de firma​

Si configuras un secreto al crear el webhook, Frihet firma cada payload con HMAC-SHA256. El proceso de verificación consiste en:

  1. Tomar el cuerpo de la petición en bruto (raw body)
  2. Calcular el HMAC-SHA256 usando el secreto como clave
  3. Comparar el resultado con el valor de la cabecera X-Frihet-Signature (sin el prefijo sha256=)

Con el SDK (recomendado)​

import { Webhooks } from '@frihet/sdk';

app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = Webhooks.verifySignature(
req.body,
req.headers['x-frihet-signature'],
process.env.FRIHET_WEBHOOK_SECRET,
);

if (!isValid) return res.status(401).send('Firma invalida');

const event = JSON.parse(req.body.toString());
console.log(req.headers['x-frihet-event'], event.data);
res.sendStatus(200);
});

Ejemplo manual en Node.js​

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');

const receivedSignature = signature.replace('sha256=', '');

return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex'),
);
}

// Uso en un servidor Express
app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-frihet-signature'];
const secret = process.env.FRIHET_WEBHOOK_SECRET;

if (!signature || !verifyWebhookSignature(req.body.toString(), signature, secret)) {
return res.status(401).send('Firma invalida');
}

const event = JSON.parse(req.body.toString());
const eventType = req.headers['x-frihet-event'];

console.log(`Evento recibido: ${eventType}`, event.data);

// Procesar el evento segun su tipo
switch (eventType) {
case 'invoice.paid':
// Actualizar tu sistema de contabilidad
break;
case 'expense.created':
// Notificar al equipo financiero
break;
}

res.status(200).send('OK');
});

Ejemplo en Python​

import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = 'tu-secreto-aqui'

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()

received = signature.replace('sha256=', '')

return hmac.compare_digest(expected, received)

@app.route('/webhook/frihet', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Frihet-Signature', '')
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
abort(401)

event = request.get_json()
event_type = request.headers.get('X-Frihet-Event')

print(f'Evento recibido: {event_type}')

if event_type == 'invoice.paid':
# Actualizar tu sistema de contabilidad
pass
elif event_type == 'quote.accepted':
# Convertir presupuesto a factura
pass

return 'OK', 200

Importante: Usa siempre una comparación en tiempo constante (timingSafeEqual en Node.js, compare_digest en Python) para evitar ataques de timing.


Política de reintentos​

Si la entrega de un webhook falla (código de respuesta no-2xx o timeout), Frihet reintenta automáticamente con backoff exponencial:

IntentoRetardoTiempo acumulado
1 (inicial)inmediato0s
2 (primer reintento)2 segundos2s
3 (segundo reintento)4 segundos6s
  • Máximo 3 intentos por entrega (1 inicial + 2 reintentos)
  • Retardo máximo de 30 segundos entre reintentos
  • Timeout de 30 segundos por petición
  • Si los 3 intentos fallan, la entrega se marca como failed
  • En cada reintento se incluye la cabecera X-Frihet-Retry-Attempt con el número de intento

Los reintentos se procesan mediante un job que se ejecuta cada 5 minutos (hasta 50 entregas pendientes por ejecución), garantizando la fiabilidad incluso si el proceso principal se reinicia.

Circuit breaker​

Si un webhook acumula 3 fallos consecutivos, Frihet lo pausa automáticamente para proteger tanto tu endpoint como el sistema de entregas.

  • El webhook pasa a estado paused con pausedReason: "consecutive_failures"
  • No se envían más entregas hasta que lo reactives manualmente
  • Puedes reactivar el webhook desde el panel o vía API (PATCH /v1/webhooks/:id con active: true)

Puedes consultar el historial de entregas de cada webhook desde el panel de Frihet, incluyendo el código de respuesta, el cuerpo de la respuesta (primeros 1000 caracteres) y los errores de cada intento.


Pruebas​

Desde el panel de Frihet puedes enviar un evento de prueba a cualquier webhook configurado. El payload de prueba tiene la siguiente estructura:

{
"event": "webhook.test",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"message": "This is a test webhook from Frihet ERP"
}
}

Esto permite verificar que tu endpoint esta accesible, que la firma se valida correctamente y que tu sistema procesa los eventos sin errores.


Buenas practicas​

Responde rápido​

Tu endpoint debe responder con un código 200 lo antes posible. Si necesitas realizar un procesamiento pesado (enviar emails, actualizar bases de datos externas, etc.), acepta el evento y procésalo de forma asíncrona en una cola de trabajo.

Gestiona la idempotencia​

Es posible que un mismo evento se entregue más de una vez (por ejemplo, si tu servidor respondió con un timeout pero proceso el evento). Usa el campo X-Frihet-Delivery-Id como clave de idempotencia para evitar duplicados.

Verifica la firma siempre​

Nunca confíes en un webhook sin verificar la cabecera X-Frihet-Signature. Cualquier actor con acceso a tu URL podría enviar payloads falsos.

Usa HTTPS​

En producción, tu endpoint debe estar protegido con HTTPS. Frihet rechaza URLs HTTP (excepto en desarrollo local).

Monitoriza los fallos​

Revisa periódicamente los logs de entrega en el panel de Frihet. Si ves entregas fallidas de forma recurrente, verifica que tu endpoint esta disponible y responde en menos de 30 segundos.

Filtra los eventos​

Suscríbete solo a los eventos que necesitas. Cada webhook puede escuchar uno o varios tipos de evento. Cuantos menos eventos innecesarios proceses, menor sera la carga en tu servidor.


Depuración de entregas​

Frihet registra el resultado de cada entrega de webhook. Puedes consultarlo desde el panel:

  1. Ve a Integraciones → Webhooks
  2. Pulsa sobre el webhook que quieres inspeccionar
  3. Abre la pestaña Entregas

Cada entrada muestra:

  • Código de respuesta HTTP devuelto por tu endpoint
  • Tiempo de respuesta en milisegundos
  • Cuerpo de la respuesta (primeros 1000 caracteres)
  • Fecha y hora de cada intento (incluidos los reintentos)
  • Estado final: delivered, retrying o failed

Problemas habituales​

SíntomaCausa probableSolución
Todas las entregas fallan con timeoutEndpoint tarda más de 30s en responderAcepta el evento con un 200 inmediato y procesa de forma asíncrona
Error SSL/TLSCertificado expirado o cadena incompletaRenueva el certificado y verifica la cadena con openssl s_client
Código 403 sistemáticoFirewall bloqueando peticiones POST entrantesPermite trafico desde las IPs de Google Cloud (us-central1)
Código 502/503Servidor caído o en mantenimientoRevisa los logs de tu servidor y verifica que el proceso esta activo

Botón de prueba​

En la pantalla de detalle de cada webhook, el botón Enviar prueba envía un evento sintético webhook.test a tu URL. Usa este botón para verificar:

  • Que la URL es accesible desde internet
  • Que la firma HMAC se valida correctamente
  • Que tu servidor responde con un código 2xx

El resultado de la prueba aparece inmediatamente en el log de entregas.


Resolución de problemas​

No recibo webhooks​

  1. Verifica que la URL del webhook es correcta y accesible desde internet
  2. Comprueba que el webhook esta en estado activo en el panel
  3. Revisa los logs de entrega para ver si hay errores
  4. Si usas un firewall, asegúrate de que las peticiones POST entrantes desde Google Cloud (us-central1) están permitidas

Las firmas no coinciden​

  1. Verifica que estas usando el raw body de la petición (antes de parsear el JSON)
  2. Confirma que el secreto en tu código coincide con el configurado en el panel de Frihet
  3. No modifiques ni reformatees el body antes de verificar la firma
  4. Revisa que tu framework no esta parseando automáticamente el body antes de que puedas acceder al raw

Los reintentos no llegan​

  1. Los reintentos se procesan cada 5 minutos. Si tu endpoint estuvo caído brevemente, puede que ya se hayan agotado los intentos
  2. Revisa el historial de entregas para confirmar el estado de cada intento
  3. Si los 3 intentos fallaron, la entrega no se reintentara de nuevo automáticamente

Payload demasiado grande​

Si el recurso asociado al evento es muy grande (muchas líneas en una factura, campos extensos), el payload puede superar el límite de 100 KB y la entrega sera rechazada. Simplifica los datos del recurso o contacta con soporte si necesitas un límite superior.


Relacionado: Configuración de webhooks · API REST · Servidor MCP