Visión general
Un webhook es Faturei Hoje avisándole a su sistema cuando algo pasa en la empresa: un cliente registrado, una venta pagada, una orden de servicio concluida. En lugar de que usted le pregunte a la API cada tanto, la API envía un POST con el evento a una dirección suya, el endpoint.
Cómo llega el evento
Sección titulada «Cómo llega el evento»- Alguien escribe algo: por el panel, por la API, por WhatsApp, por una automatización o por una rutina automática.
- En esa misma escritura, el evento se registra con el objeto tal como el
GETde ese recurso lo devolvería en ese instante, con las mismas reglas de campos. - El evento se convierte en una entrega para cada endpoint activo y suscrito a él.
- La entrega se firma y se envía. Si falla, se reintenta según un calendario de unos 3 días. Vea Entregas y reintentos.
El evento solo se registra cuando la empresa tiene, en ese momento, al menos un endpoint activo y suscrito a ese tipo de evento. Lo que pasa mientras ningún endpoint escucha no se convierte en evento, ni siquiera después.
Crear un endpoint
Sección titulada «Crear un endpoint»Por la API, con POST /public/v1/webhook_endpoints:
curl -X POST "https://api.fatureihoje.com/public/v1/webhook_endpoints" \ -H "Authorization: Bearer $FH_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 0f5d3c1e-7a2b-4e8f-9c6d-1b2a3c4d5e6f" \ -d '{ "url": "https://erp.suempresa.com/webhooks/faturei-hoje", "description": "ERP", "events": ["sale.created", "sale.paid", "client.created"] }'La respuesta trae el endpoint y el campo secret, que empieza con whsec_. Por la API, el secreto solo aparece aquí y en la rotación: guárdelo en su servidor, es con él que usted verifica la firma. Repetir la misma llamada con la misma Idempotency-Key devuelve el endpoint sin el secreto. En el panel, el dueño y los administradores pueden revelar el secreto de nuevo, confirmando la contraseña.
eventses la lista de nombres del Catálogo de eventos, o["*"]para todos, incluidos los que entren al catálogo después.*va solo: junto a otro nombre, la respuesta es 422.- Gestionar webhooks es tarea del dueño y de los administradores, en el panel (en Configuración → API → Webhooks) o con una clave de API de un miembro con ese rol. Las rutas de lectura también exigen el rol, porque la lista de endpoints dice adónde van los datos de la empresa. En el panel, cambiar la URL o los eventos pide la contraseña.
- Recibir un evento es leer su objeto. Por eso, para suscribirse, la clave necesita
<modulo>:readde cada módulo de los eventos elegidos (con*, de todos). Sin eso, la respuesta es 403. - Cada endpoint tiene su propio secreto. Dos endpoints nunca comparten secreto.
- El dueño y los administradores reciben un correo por cada endpoint creado.
Para probar la conexión, POST /public/v1/webhook_endpoints/{id}/ping envía un evento ping de verdad solo a ese endpoint, por el mismo camino que cualquier evento (firma, reintentos, registro de entregas). No es un entorno de prueba: solo confirma que la dirección recibe y verifica. Un endpoint desactivado responde 409 conflict.
Las rutas completas, campo por campo, están en la Referencia.
Adónde puede ir la entrega
Sección titulada «Adónde puede ir la entrega»La URL pasa por una verificación en el registro, y la misma verificación se repite en cada intento de envío, porque el dueño del dominio puede cambiar el DNS después del registro.
- Solo
https://. Una direcciónhttp://se rechaza. - Cualquier puerto de 1 a 65535.
- Sin usuario y contraseña en la URL (
https://usuario:contraseña@...): se rechaza, porque quedaría guardado y aparecería al leer el endpoint. - El nombre tiene que resolver a una dirección pública de internet.
localhost, nombres sin punto, nombres terminados en.local,.internal,.lany similares, e IP privada, reservada o de metadatos de nube se rechazan. Se verifican todos los registros A y AAAA del nombre: basta uno interno para rechazar. - El certificado TLS tiene que ser válido. Un certificado autofirmado o vencido hace fallar la entrega.
- Las redirecciones no se siguen. Un
3xxcuenta como falla.
En el registro, la URL rechazada responde 422 con param: "url". En el envío, el intento falla y el motivo aparece en el registro de entregas.
El formato de la entrega
Sección titulada «El formato de la entrega»Cada entrega es un POST con estos encabezados, según el estándar Standard Webhooks:
| Encabezado | Valor |
|---|---|
webhook-id | Id del evento, evt_ seguido de 32 caracteres hexadecimales. El mismo id del cuerpo |
webhook-timestamp | Momento de este intento, en segundos Unix |
webhook-signature | La firma, v1, seguido del HMAC en base64. Vea Verificar la firma |
content-type | application/json |
user-agent | FatureiHoje-Webhooks/1.0 |
Y este cuerpo:
{ "id": "evt_9b2f4c7e1a3d4f6b8c0e2a4d6f8b1c3e", "type": "client.updated", "api_version": "v1", "timestamp": "2026-09-16T14:30:00.000Z", "organization_id": "5c1e8f2a-3b4d-4c6e-8f0a-1b2c3d4e5f60", "data": { "object": { "object": "client", "id": "0d9a7c5e-3f1b-4d2a-8c6e-4a2b0c8e6f1d", "...": "..." }, "changed_fields": ["email", "phone"] }}| Campo | Qué es |
|---|---|
id | Id del evento. El mismo en todos los intentos y en el reenvío manual |
type | Nombre del evento, del Catálogo de eventos |
api_version | Versión del contrato del objeto. Hoy, siempre v1 |
timestamp | Cuándo ocurrió el evento, en ISO 8601 UTC. No cambia entre intentos |
organization_id | La empresa del evento |
data.object | El objeto, igual al que el GET del recurso devolvería en ese instante |
data.changed_fields | Solo en los eventos .updated: qué campos cambiaron |
data.object_truncated | Solo aparece cuando el objeto llegó resumido, y entonces vale true |
Fíjese en la diferencia entre los dos horarios: webhook-timestamp es el horario del envío y cambia en cada intento (es lo que protege contra la repetición maliciosa); timestamp, en el cuerpo, es el horario del hecho, y es por él que usted ordena.
El ping tiene el mismo formato, con type: "ping" y un objeto propio:
{ "id": "evt_4e6a8c0b2d4f4a6c8e0b2d4f6a8c0e2b", "type": "ping", "api_version": "v1", "timestamp": "2026-09-16T14:30:00.000Z", "organization_id": "5c1e8f2a-3b4d-4c6e-8f0a-1b2c3d4e5f60", "data": { "object": { "object": "ping", "webhook_endpoint_id": "7f3a1c5e-9b2d-4f6a-8c0e-2b4d6f8a0c1e" } }}changed_fields
Sección titulada «changed_fields»En los eventos .updated, changed_fields lista los campos del objeto que cambiaron en esa escritura, en orden alfabético. updated_at nunca entra en la lista, porque cambia en cada escritura y no dice qué cambió.
Una escritura puede generar más de un evento. Si el estado de una orden de servicio cambió junto con la descripción, salen service_order.status_changed y service_order.updated, y el changed_fields del segundo trae los dos campos. Las reglas de cada área están en el Catálogo de eventos.
Una escritura que no cambia nada en el objeto no genera .updated: guardar un registro sin cambiar ningún campo no envía evento.
El tope de 256 KB y object_truncated
Sección titulada «El tope de 256 KB y object_truncated»El cuerpo de la entrega tiene un tope de 256 KB, contados en bytes. Cuando el objeto no cabe, data.object llega solo con object e id, y data.object_truncated llega en true:
{ "data": { "object": { "object": "sale", "id": "3c5e7a9b-1d3f-4b5d-8f1a-3c5e7a9b1d3f" }, "object_truncated": true }}Con object_truncated, busque el objeto por la API (GET /public/v1/sales/{id}, en el ejemplo). Llega como está ahora, que puede ser distinto de como estaba en el instante del evento.
Eventos que llegan resumidos
Sección titulada «Eventos que llegan resumidos»Algunos caminos de escritura no arman el objeto completo. De ellos, el evento sale siempre resumido, en el mismo formato del tope de arriba: data.object = { object, id } y data.object_truncated: true. Son:
- lo que escribe el asistente de WhatsApp;
- lo que escriben las automatizaciones;
- lo que viene de Open Finance (importación y conciliación bancaria);
- las rutinas automáticas: la que marca movimientos y ventas como vencidos y la que genera los meses siguientes de las recurrencias sin fin;
- y, raras veces, cualquier otra escritura en la que el objeto no se pudo armar en ese momento.
El tipo del evento es el correcto (task.created, financial_transaction.updated…), pero el objeto no llega. Búsquelo por la API. En un .updated resumido, changed_fields llega vacío, porque sin el objeto no hay cómo saber qué campos cambiaron.
Trate object_truncated: true siempre de la misma forma, venga del tope de tamaño o de uno de estos caminos: busque el objeto por la API.
Lo que no genera evento
Sección titulada «Lo que no genera evento»Algunos cambios en el objeto ocurren sin evento propio. Son consecuencia de otra escritura, y el evento de esa otra escritura es lo que usted recibe.
- Vínculo borrado junto con el padre. Cuando se elimina un registro, lo que apuntaba a él pierde el vínculo (el campo pasa a
null) sin.updated. Por ejemplo: las tareas y las citas de un cliente, de una orden de servicio, de un movimiento o de un miembro que fue eliminado, y la orden de servicio de una cita eliminada. Usted recibe el.deleteddel padre. - Eliminación de producto. Los ítems de venta pierden el
product_idy los movimientos de stock del producto se eliminan, sinsale.updatednistock.changed. Usted recibe elproduct.deleted. - Renombrar una categoría no genera
product.updatedniservice.updateden sus ítems. - Número de recibo. El
receipt_numberde un cobro de la venta (payments[].receipt_number) pasa denulla un número (R-0001, por ejemplo) cuando alguien genera en el panel, por primera vez, el recibo del movimiento de ese cobro. Esto ocurre sinsale.updated. - Factura.
invoice_lockedde la venta yhas_active_invoicede los cobros cambian cuando se emite o se cancela una factura, sinsale.updated. is_overduede la tarea cambiando solo, con el paso del tiempo, no generatask.updated.- Vencimiento del presupuesto. El presupuesto vencido solo cambia de estado cuando alguien abre la lista de presupuestos. El
quote.status_changeddel vencimiento sale en ese momento, y no el día en que vence.
Cuando el dato tiene que estar exacto, vuelva a leer el recurso por la API en lugar de confiar solo en los eventos. Vea Buenas prácticas.
Quién recibe qué
Sección titulada «Quién recibe qué»Cada endpoint guarda quién lo suscribió: el miembro, y la clave de API cuando la suscripción vino por la API. Quien suscribió es quien creó el endpoint o, después, quien cambió la URL o los eventos, o lo volvió a activar.
Antes de cada envío, Faturei Hoje verifica si quien suscribió todavía puede leer el módulo de ese evento: la clave sigue activa y con <modulo>:read, y el miembro sigue activo y con acceso al módulo, con las mismas reglas del panel. Si ya no puede:
- la entrega de ese evento se cierra sin enviarse, con estado
failedy errorsubscriber_access_losten el registro; - eso no gasta intento y no cuenta como falla del endpoint, que sigue activo;
- los eventos de los otros módulos, que quien suscribió todavía lee, siguen llegando;
- el dueño y los administradores reciben un correo de aviso, y el aviso solo vuelve a salir después de que una entrega tenga éxito y el acceso se pierda de nuevo.
Para volver a recibir, devuelva el acceso a quien suscribió, o edite el endpoint con un miembro (o una clave) que tenga el acceso: quien edita la URL o los eventos pasa a ser quien suscribió.
Los eventos de venta (sale.*) nunca llevan account_id ni finance_transactions, aunque quien suscribió lea finanzas: suscribirse a ventas exige leer solo ventas. Los movimientos tienen sus propios eventos, financial_transaction.*, que exigen leer finanzas.
La empresa que pasa a un plan sin la API deja de recibir entregas, incluido el ping: se cierran sin envío, con el error plan_without_api.
Rotación y revocación de la clave de API
Sección titulada «Rotación y revocación de la clave de API»- Rotación con convivencia (cualquier plazo mayor que cero, como las opciones de 1 hora y de 24 horas del panel): los endpoints suscritos con la clave antigua pasan a la clave nueva en la misma acción, y nada deja de llegar.
- Rotación “Detener ahora”, el gesto de quien sospecha que la clave se filtró: los endpoints suscritos con la clave antigua se pausan, porque quien obtuvo la clave pudo haber registrado un endpoint para recibir sus datos. Aparecen con
status: "disabled"ydisabled_reason: "emergency_key_rotation", y el dueño y los administradores reciben un correo con la lista (solo el dominio de cada URL). Revise la lista y vuelva a activar lo que sea suyo. - Clave encontrada por GitHub en un repositorio público, cuando la inscripción en el programa de búsqueda de secretos de GitHub esté activa: la clave se revoca automáticamente y los endpoints suscritos con ella se pausan de la misma forma, con el mismo
disabled_reason: "emergency_key_rotation", y el dueño y los administradores reciben un correo. La inscripción todavía no está activa. - Clave revocada o vencida (fuera del caso anterior): los endpoints suscritos con ella dejan de recibir, por la regla de acceso de arriba.
Vea Rotación.
Límites
Sección titulada «Límites»Cuántos endpoints puede tener registrados la empresa, activos o no:
| Plan | Endpoints de webhook |
|---|---|
| Gratis | 0 |
| Start | 3 |
| Pleno | 10 |
| Supra | 25 |
En el envío:
- hasta 5 entregas simultáneas por endpoint;
- 1 entrega a la vez para el endpoint que está fallando, hasta que una entrega a él tenga éxito;
- hasta 10 entregas simultáneas por empresa, sumando todos los endpoints. Un endpoint lento no frena la cola de las otras empresas;
- el endpoint que solo falla durante 3 días seguidos se desactiva, y el dueño y los administradores reciben un correo. Vea Desactivación automática.
Los datos enviados pasan a ser responsabilidad de quien configuró
Sección titulada «Los datos enviados pasan a ser responsabilidad de quien configuró»Cada entrega lleva datos de la empresa fuera de Faturei Hoje, al sistema al que apunta el endpoint. Desde el momento en que el dato llega a ese sistema, pasa a ser responsabilidad de la empresa que configuró la integración, incluso ante la LGPD (la ley brasileña de protección de datos): quién lo guarda, por cuánto tiempo, quién accede y cómo se descarta.
De nuestro lado, los eventos y el registro de entregas se guardan durante 30 días y después se eliminan. El envío corre en nuestra propia infraestructura, sin ningún servicio de terceros en el medio.
Próximo paso
Sección titulada «Próximo paso»- Verificar la firma: el código para Node.js, PHP y Python.
- Catálogo de eventos: cada evento, cuándo sale y el ejemplo del cuerpo.
- Entregas y reintentos: qué cuenta como éxito, el calendario y el reenvío.
- Buenas prácticas: responder rápido, ignorar duplicados y ordenar.