Ir al contenido principal

Webhooks

Conecta tus aplicaciones con Holded mediante webhooks y recibe notificaciones en tiempo real cuando ocurren eventos como facturas, contactos o cambios de stock, sin necesidad de consultar la API constantemente.

Escrito por Natalia López

Los webhooks permiten que tus aplicaciones e integraciones reaccionen en tiempo real a lo que ocurre en Holded. En lugar de consultar la API cada pocos minutos para detectar cambios, le indicas a Holded una URL y te enviamos una petición en el momento en que sucede un evento: se crea una factura, se actualiza un contacto, cambia el stock, etc.

Este artículo explica cómo crear un webhook desde Holded, qué información tienes disponible después y cómo leer y verificar las peticiones que enviamos.

Es una funcionalidad orientada a desarrolladores. Para usar webhooks necesitas un endpoint: una URL pública https:// capaz de recibir peticiones POST.


Cómo funcionan

1. Creas un endpoint en Holded indicando una URL y eliges qué eventos quieres recibir.

2. Cuando ocurre uno de esos eventos, Holded envía una petición POST a tu URL con un cuerpo JSON que resume lo que ha cambiado.

3. Tu endpoint responde con cualquier código de estado 2xx para confirmar la recepción.

Cada entrega va firmada, de modo que puedes verificar que realmente proviene de Holded.


Crear un webhook

Los webhooks se gestionan desde Configuración → Desarrolladores → Webhooks.

1. Ve a Configuración → Webhooks y pulsa + Nuevo webhook.

2. Rellena el formulario:

Descripción — un nombre para identificar el webhook.

URL del endpoint — la dirección https:// pública que recibirá los eventos.

3. En Eventos, selecciona a qué quieres suscribirte. Los eventos se agrupan por área (Factura, Pedido de venta, Stock, Contacto…). Despliega cada área y marca las acciones que te interesen: Crear, Actualizar, Eliminar o Aprobar. Puedes usar el buscador para localizarlos rápido.

4. A la derecha, en Eventos seleccionados, verás un resumen de todo lo que has marcado, agrupado por área.

5. Pulsa Crear.

El secreto de firma

Al crear el webhook, Holded muestra una sola vez el secreto de firma (con el prefijo whsec_). Cópialo y guárdalo en un lugar seguro en ese momento: lo necesitarás para verificar que las peticiones provienen realmente de Holded.

Más adelante podrás encontrarlo en la configuración del webhook, pero aparecerá parcialmente enmascarado (por ejemplo, whsec_••••f776), así que conviene guardar el valor completo ahora. Pulsa Listo para terminar; el webhook aparecerá en el listado con el estado Activo.

Puedes crear más de un endpoint (por ejemplo, uno para eventos de facturación y otro para inventario) y editarlos o eliminarlos cuando quieras.

☝️ El webhook se configura por cada cuenta de Holded, no a nivel de organización. Si tienes varias cuentas, configura un webhook en cada una. Pueden apuntar a la misma URL: cada entrega incluye la cabecera x-holded-webhook-account-id para que tu sistema identifique de qué cuenta procede.


Gestionar un webhook

Desde el listado, pulsa sobre un webhook para abrir su detalle. Con el conmutador junto al nombre puedes activarlo o desactivarlo en cualquier momento. El detalle tiene tres pestañas: Resumen, Entregas y Configuración.

Resumen

Muestra la salud del endpoint en el periodo seleccionado (24h, 7d o 30d):

Tasa de éxito — porcentaje de entregas correctas sobre el total.

Latencia P50 — tiempo de respuesta mediano de tu endpoint.

Fallos consecutivos — entregas fallidas seguidas (útil para detectar un endpoint caído).

Pico por día — máximo de entregas en un día.

Entregas por hora — gráfico de volumen en el tiempo.

Por estado — recuento de entregas Correctas, Advertencias y Errores.

Entregas

Es el registro de entregas del webhook. Puedes filtrar por estado (Todas, Correctas, Advertencias, Errores), acotar el periodo (24h/7d/30d) y buscar por evento, ID o payload. La tabla muestra, para cada entrega, su Estado, el Evento · ID, la Hora y la Latencia.

Esta pestaña es también la forma más fiable de ver el contenido exacto que recibe tu endpoint: abre cualquier entrega para inspeccionar su payload real.

Configuración

Reúne los datos y ajustes del webhook:

Identidad — ID, versión de API, reintentos, fechas de creación y actualización, e IP de origen.

Eventos suscritos — resumen de las áreas y acciones a las que está suscrito. Pulsa Modificar para cambiar la selección.

Signing secret — el secreto de firma, enmascarado, con un botón Copiar.

Eliminar webhook — elimina el endpoint. Esta acción no se puede deshacer.


La petición webhook

Cada evento se entrega como una petición HTTP POST con un cuerpo JSON. Holded incluye estas cabeceras en cada petición:

Cabecera

Tipo

Descripción

x–holded-webhook-event

string

Nombre del evento, p. ej. invoice.create

x–holded-webhook-date

string (ISO 8601)

Momento del envío, p. ej. 2026-06-18T11:10:35Z

x-holded-webhook-signature

string

Firma HMAC-SHA256 con prefijo sha256= (ver abajo)

x-holded-webhook-id

string

Identificador único del evento; úsalo como clave de idempotencia

x-holded-webhook-account-id

string

Identificador de la cuenta de Holded que origina el evento

x-holded-webhook-version

string

Versión del sistema de webhooks (p. ej. v1)

Tu endpoint debe devolver cualquier código 2xx para confirmar la entrega. Una respuesta distinta de 2xx (o un timeout) se considera una entrega fallida, y Holded reintenta la entrega hasta 5 veces.

Responde rápido. Confirma primero (devuelve 2xx) y procesa después de forma asíncrona. El trabajo pesado dentro de la petición puede provocar timeouts y reintentos innecesarios.

Como una entrega puede reintentarse, diseña tu handler para que sea idempotente: recibir el mismo evento dos veces no debe duplicar efectos. La cabecera x-holded-webhook-id es una clave de deduplicación fiable.


Verificar la firma

Cualquiera que conozca la URL de tu endpoint podría enviarle peticiones, así que conviene verificar que cada una proviene realmente de Holded antes de procesarla.

Holded firma cada petición con el secreto de firma (whsec_…) generado al crear el endpoint. La cabecera x-holded-webhook-signature contiene el digest hexadecimal HMAC-SHA256 —precedido del prefijo sha256=— de la cadena:

<x-holded-webhook-date>.<cuerpo de la petición sin procesar>

Para verificar una petición:

1. Lee el cuerpo sin procesar (raw).

2. Calcula HMAC-SHA256(cadena, tu_secreto) en hexadecimal.

3. Elimina el prefijo sha256= de x-holded-webhook-signature y compara ambos valores con una comparación en tiempo constante.


El payload: un resumen, no datos completos

El cuerpo del webhook es una notificación resumida del registro afectado, no el objeto completo. Por ejemplo, el payload de una factura se parece a esto:

{

"id": "6a33d22b2fa645a14c024bd0",

"type": "invoice",

"documentNumber": null,

"date": "2026-06-18T00:00:00+02:00",

"dueDate": null,

"contactId": "68b89460c77cebb2cb0fb799",

"contactName": "Empresa Ejemplo S.L.",

"total": "121",

"subtotal": "100",

"isDraft": true

}

Para los datos completos —líneas del documento, etiquetas, moneda, estado de pago, cuenta contable, campos personalizados, etc.— debes hacer una llamada GET al recurso correspondiente usando el id del payload. En otras palabras, el webhook te dice qué ha cambiado y cuándo; la API te da el detalle.

Puedes consultar la estructura completa de cada payload, campo a campo, en la referencia para desarrolladores: www.holded.com/developers/webhooks


Catálogo de eventos

El nombre de cada evento sigue el patrón <área>.<acción> y viaja en la cabecera x-holded-webhook-event. En la interfaz, el área aparece con su nombre en español y la acción como Crear / Actualizar / Eliminar / Aprobar; el nombre técnico del evento es el de la columna derecha.

• .create (Crear) — se ha creado un registro nuevo.

• .update (Actualizar) — ha cambiado un registro existente.

• .delete (Eliminar) — se ha eliminado un registro.

• .approve (Aprobar) — se ha aprobado el documento (solo en los tipos con estado de aprobación).

Documentos de venta

Área

Eventos

Factura

invoice.create · invoice.update · invoice.delete · invoice.approve

Ticket de venta

salesreceipt.create · salesreceipt.update · salesreceipt.delete · salesreceipt.approve

Factura rectificativa

creditnote.create · creditnote.update · creditnote.delete · creditnote.approve

Presupuesto

estimate.create · estimate.update · estimate.delete

Proforma

proform.create · proform.update · proform.delete

Pedido de venta

salesorder.create · salesorder.update · salesorder.delete

Albarán

waybill.create · waybill.update · waybill.delete

Documentos de compra

Área

Eventos

Gasto

purchase.create · purchase.update · purchase.delete · purchase.approve

Pedido de compra

purchaseorder.create · purchaseorder.update · purchaseorder.delete

Nota de crédito de compra

purchaserefund.create · purchaserefund.update · purchaserefund.delete · purchaserefund.approve

Nota de recepción

receiptnote.create · receiptnote.update · receiptnote.delete · receiptnote.approve

Recibo

receipt.create · receipt.update · receipt.delete

Inventario y catálogo

Área

Eventos

Stock

stock.update

Producto

product.create · product.update · product.delete

Contactos

Área

Eventos

Contacto

contact.create · contact.update · contact.delete


Cobertura y limitaciones

Conviene conocer qué cubre el sistema de webhooks y qué no, para diseñar bien tu integración.

Cobertura completa. Todos los tipos de documento (facturas, gastos, presupuestos, proformas, etc.) emiten create, update, delete y approve según corresponda, y los contactos emiten create, update y delete. Estos recursos pueden mantenerse sincronizados por webhook tras una carga inicial.

Cobertura parcial.

Producto — existen product.create y product.update, pero **no existe product.delete**. Los productos eliminados no se detectan por webhook; para ello necesitas un sondeo periódico que compare el catálogo.

Stock — solo existe stock.update, que refleja movimientos, no el estado inicial. Antes de empezar a depender del webhook necesitas una carga inicial del stock de partida.

Sin cobertura. Estos recursos no emiten webhooks; si los sincronizas, necesitas sondeo periódico: almacenes, servicios, proyectos, tareas, tiempos de proyecto, pagos, plan contable, libro diario y facturas recurrentes.

Pagos. No hay un evento dedicado a los pagos. Registrar un pago dispara un invoice.update o purchase.update, y el estado de pago no viene en el payload: haz un GET del documento para conocerlo.


Resolución de problemas

¿No recibes eventos? Comprueba que la URL del endpoint es pública, usa https://, devuelve 2xx rápidamente y que los eventos están seleccionados para ese webhook. Revisa la pestaña Entregas para ver el detalle de cada intento.

¿Falla la verificación de la firma? Asegúrate de leer las cabeceras x-holded-webhook-*, de **quitar el prefijo sha256= antes de comparar, y de firmar el cuerpo en bruto** (no un JSON re-serializado) uniendo timestamp y body con un punto.

¿Eventos duplicados? Es lo esperado en los reintentos: haz tu handler idempotente usando x-holded-webhook-id.

¿Ha quedado contestada tu pregunta?