Webhooks
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 enviara un POST con los datos del evento en el momento en que se produzca.
Configuracion
- Ve a Configuracion > Webhooks en tu cuenta de Frihet
- Pulsa Crear webhook
- Introduce un nombre descriptivo, la URL de destino y selecciona los eventos que quieres recibir
- Opcionalmente, define un secreto para la verificacion HMAC (recomendado)
- Guarda el webhook
Requisitos de la URL:
- Debe usar HTTPS (se permite HTTP solo para
localhosty127.0.0.1durante el desarrollo) - No se permiten IPs privadas (10.x, 172.16-31.x, 192.168.x) para evitar ataques SSRF
- Debe responder con un codigo 2xx en menos de 30 segundos
Limites:
- Maximo 20 webhooks por cuenta
- Payload maximo de 100 KB por entrega
Tipos de evento
Frihet emite 14 tipos de evento, agrupados por recurso.
Facturas
| Evento | Descripcion |
|---|---|
invoice.created | Se ha creado una nueva factura |
invoice.updated | Se ha modificado una factura existente |
invoice.paid | Una factura ha sido marcada como pagada |
invoice.overdue | Una factura ha superado su fecha de vencimiento |
Gastos
| Evento | Descripcion |
|---|---|
expense.created | Se ha registrado un nuevo gasto |
expense.updated | Se ha modificado un gasto existente |
Presupuestos
| Evento | Descripcion |
|---|---|
quote.created | Se ha creado un nuevo presupuesto |
quote.updated | Se ha modificado un presupuesto existente |
quote.accepted | Un cliente ha aceptado un presupuesto |
quote.rejected | Un cliente ha rechazado un presupuesto |
Clientes
| Evento | Descripcion |
|---|---|
client.created | Se ha dado de alta un nuevo cliente |
client.updated | Se han modificado los datos de un cliente |
Productos
| Evento | Descripcion |
|---|---|
product.created | Se ha creado un nuevo producto o servicio |
product.updated | Se ha modificado un producto o servicio existente |
Estructura del payload
Cada entrega de webhook es una peticion 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 peticion de webhook incluye las siguientes cabeceras:
| Cabecera | Descripcion | Ejemplo |
|---|---|---|
Content-Type | Tipo de contenido | application/json |
X-Frihet-Event | Tipo de evento | invoice.paid |
X-Frihet-Delivery-Id | Identificador unico de la entrega | d4e5f6a7b8c9 |
X-Frihet-Timestamp | Marca de tiempo ISO 8601 | 2026-02-12T14:30:00.000Z |
X-Frihet-Signature | Firma HMAC-SHA256 del payload | sha256=a1b2c3d4e5f6... |
La cabecera X-Frihet-Signature solo se incluye si has configurado un secreto en el webhook.
Verificacion de firma
Si configuras un secreto al crear el webhook, Frihet firma cada payload con HMAC-SHA256. El proceso de verificacion consiste en:
- Tomar el cuerpo de la peticion en bruto (raw body)
- Calcular el HMAC-SHA256 usando el secreto como clave
- Comparar el resultado con el valor de la cabecera
X-Frihet-Signature(sin el prefijosha256=)
Ejemplo 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 comparacion en tiempo constante (timingSafeEqual en Node.js, compare_digest en Python) para evitar ataques de timing.
Politica de reintentos
Si la entrega de un webhook falla (codigo de respuesta no-2xx o timeout), Frihet reintenta automaticamente con backoff exponencial:
| Intento | Retardo | Tiempo acumulado |
|---|---|---|
| 1 (inicial) | inmediato | 0s |
| 2 (primer reintento) | 2 segundos | 2s |
| 3 (segundo reintento) | 4 segundos | 6s |
- Maximo 3 intentos por entrega (1 inicial + 2 reintentos)
- Retardo maximo de 30 segundos entre reintentos
- Timeout de 30 segundos por peticion
- Si los 3 intentos fallan, la entrega se marca como
failed
Los reintentos se procesan mediante un job que se ejecuta cada 5 minutos, garantizando la fiabilidad incluso si el proceso principal se reinicia.
Puedes consultar el historial de entregas de cada webhook desde el panel de Frihet, incluyendo el codigo 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 rapido
Tu endpoint debe responder con un codigo 200 lo antes posible. Si necesitas realizar un procesamiento pesado (enviar emails, actualizar bases de datos externas, etc.), acepta el evento y procesalo de forma asincrona en una cola de trabajo.
Gestiona la idempotencia
Es posible que un mismo evento se entregue mas de una vez (por ejemplo, si tu servidor respondio 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 confies en un webhook sin verificar la cabecera X-Frihet-Signature. Cualquier actor con acceso a tu URL podria enviar payloads falsos.
Usa HTTPS
En produccion, tu endpoint debe estar protegido con HTTPS. Frihet rechaza URLs HTTP (excepto en desarrollo local).
Monitoriza los fallos
Revisa periodicamente 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
Suscribete 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.
Resolucion de problemas
No recibo webhooks
- Verifica que la URL del webhook es correcta y accesible desde internet
- Comprueba que el webhook esta en estado activo en el panel
- Revisa los logs de entrega para ver si hay errores
- Si usas un firewall, asegurate de que las peticiones POST entrantes desde Google Cloud (
us-central1) estan permitidas
Las firmas no coinciden
- Verifica que estas usando el raw body de la peticion (antes de parsear el JSON)
- Confirma que el secreto en tu codigo coincide con el configurado en el panel de Frihet
- No modifiques ni reformatees el body antes de verificar la firma
- Revisa que tu framework no esta parseando automaticamente el body antes de que puedas acceder al raw
Los reintentos no llegan
- Los reintentos se procesan cada 5 minutos. Si tu endpoint estuvo caido brevemente, puede que ya se hayan agotado los intentos
- Revisa el historial de entregas para confirmar el estado de cada intento
- Si los 3 intentos fallaron, la entrega no se reintentara de nuevo automaticamente
Payload demasiado grande
Si el recurso asociado al evento es muy grande (muchas lineas en una factura, campos extensos), el payload puede superar el limite de 100 KB y la entrega sera rechazada. Simplifica los datos del recurso o contacta con soporte si necesitas un limite superior.