Saltar al contenido
↑↓ navegar ↵ abrir Esc cerrar
Webhooks

28 eventos por suscripción y cero suposiciones

Configuras la URL desde la app, eliges los eventos y opcionalmente añades cabeceras personalizadas para autenticar tu receptor. La forma del payload es estándar y la lista de eventos vive sembrada en la migración inicial.

Resumen

Cuando un evento ocurre dentro del espacio de trabajo, factura.com.do envía un POST con cuerpo JSON a la URL que registraste. La entrega es fire-and-forget: tu receptor responde 2xx y la entrega queda persistida en el log de ejecución de webhooks. Si responde distinto, no hay reintento automático.

Modelo de suscripción

La suscripción se administra desde la sección Webhooks del espacio de trabajo, no por API pública. Configuras URL destino, método HTTP (GET/POST/PUT), cabeceras personalizadas (nombre-valor) y selección de eventos. La app expone un endpoint de prueba interno para verificar conectividad antes de activar.

Forma del payload

Todos los eventos comparten la misma forma de cuerpo: Event, el nombre del evento; Timestamp, en ISO 8601 y en la hora local del servidor (con su desfase, no normalizada a UTC), y data, específico del evento. Las claves llegan en PascalCase.

dgii.aprobado.json
{
"Event": "dgii.aprobado",
"Timestamp": "2026-05-08T10:33:21.4830000-04:00",
"data": {
"DocumentId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"StatusId": 5
}
}

Catálogo de 28 eventos

Los 28 eventos cubren cotizaciones, facturas, notas, gastos, estados DGII y catálogo. Todos están sembrados desde la migración inicial; suscríbete solo a los que tu integración necesita.

cotizacion.created Cotizaciones

Una cotización fue creada en el espacio de trabajo.

cotizacion.updated Cotizaciones

Una cotización fue editada.

cotizacion.anulated Cotizaciones

Una cotización fue anulada.

factura.created Facturas

Una factura (E31, E32, E44, E45 o E46) fue creada.

factura.updated Facturas

Una factura existente fue modificada y guardada desde la app. edit-amount y edit-text no la disparan: emiten una nota nueva.

factura.anulated Facturas

Una factura fue anulada (void).

nota_credito.created Notas de crédito

Una nota de crédito (E34) fue emitida, también la que generan edit-amount y edit-text sobre una factura.

nota_credito.updated Notas de crédito

Una nota de crédito existente fue modificada.

nota_credito.anulated Notas de crédito

Una nota de crédito fue anulada.

nota_debito.created Notas de débito

Una nota de débito (E33) fue emitida, también la que generan edit-amount y edit-text sobre un gasto, compra o pago al exterior.

nota_debito.updated Notas de débito

Una nota de débito existente fue modificada.

nota_debito.anulated Notas de débito

Una nota de débito fue anulada.

gasto.created Gastos

Un gasto menor (E43), una compra (E41) o un pago al exterior (E47) fue registrado.

gasto.updated Gastos

Un gasto existente fue modificado.

gasto.anulated Gastos

Un gasto fue anulado.

dgii.aprobado Estado DGII

La DGII aceptó el comprobante en firme (StatusId 5).

dgii.rechazado Estado DGII

La DGII rechazó el comprobante (StatusId 6). Reconcilia con un GET al recurso para leer el motivo persistido.

dgii.en_proceso Estado DGII

La DGII aún evalúa el comprobante (StatusId 7). El polling sigue.

dgii.aceptado_condicional Estado DGII

La DGII aceptó con observaciones (StatusId 8). Registrar en bitácora.

customer.created Contactos

Un cliente fue registrado en el espacio de trabajo.

customer.updated Contactos

Un cliente fue actualizado.

customer.deactivated Contactos

Un cliente fue desactivado.

product.created Catálogo

Un producto fue agregado al catálogo.

product.updated Catálogo

Un producto fue actualizado.

product.deactivated Catálogo

Un producto fue desactivado.

supplier.created Contactos

Un proveedor fue registrado.

supplier.updated Contactos

Un proveedor fue actualizado.

supplier.deactivated Contactos

Un proveedor fue desactivado.

Forma del data por evento

El envelope (<code>Event</code>, <code>Timestamp</code>, <code>data</code>) es invariante. Lo que cambia es el contenido de <code>data</code>. Estos son los campos que emite el dispatcher; ignora los que no uses, porque pueden aparecer campos nuevos.

Eventos Campos en data
cotizacion.created, cotizacion.updated DocumentId, SequenceTypeId, StatusId
factura.created DocumentId, SequenceTypeId, StatusId, Number, Lines, CustomerId, CurrencyId, AmountDue, PaymentTermId, LimitDate, CustomerName, CustomerTaxId, CustomerPhone, CustomerEmail, CustomerAddress, Accounting
factura.updated DocumentId, SequenceTypeId, StatusId, Lines, Number, Accounting
nota_credito.created, nota_credito.updated, nota_debito.created, nota_debito.updated DocumentId, SequenceTypeId, StatusId, Number, Lines, CustomerId, SupplierId, CurrencyId, AmountDue, ModifiedReferenceId, ReferenceSecuence, ModificationCode, CustomerName, CustomerTaxId, CustomerPhone, CustomerEmail, CustomerAddress, SupplierName, SupplierTaxId, SupplierPhone, SupplierEmail, SupplierAddress, Accounting
gasto.created DocumentId, SequenceSourceId, StatusId, Number, Lines, SupplierId, CurrencyId, AmountDue, PaymentTermId, LimitDate, SupplierName, SupplierTaxId, SupplierPhone, SupplierEmail, SupplierAddress, Accounting
gasto.updated DocumentId, SequenceSourceId, StatusId, Lines, Number, Accounting
cotizacion.anulated, nota_credito.anulated, nota_debito.anulated DocumentId
factura.anulated, gasto.anulated DocumentId, WasAcceptedByDGII, CreditNoteId
dgii.aprobado, dgii.rechazado, dgii.en_proceso, dgii.aceptado_condicional DocumentId, StatusId
customer.created, customer.updated CustomerId, LegalName, TaxIdentification
supplier.created, supplier.updated SupplierId, LegalName, TaxIdentification
product.created, product.updated ProductId, Code, Description
customer.deactivated CustomerId
supplier.deactivated SupplierId
product.deactivated ProductId

Cada elemento de Lines trae LineId, ProductId, ProductVariantId, WarehouseId, Code, Order, Description, IsService, IsExempt, Quantity, UnitId, UnitPrice, Discount, DiscountPercentage, Recharge, NetAmount, TotalDiscount, TotalTax, TotalWithholdings, Subtotal, Taxes y Withholdings. Accounting es el desglose contable del encabezado: DocumentTypeId, BranchId, SequenceTypeId, IssuedAt, LimitDate, PaymentTermId, CurrencyCode, CurrencyRate, TaxableAmount, ExemptAmount, TotalDiscount, DiscountPercentage, TotalTaxes, TotalWithholdings, Total, AmountDue, Freight, Tip, TratamientoItbis, TipoBienServicioComprado y AwaitsFiscalValidation.

Receptor de ejemplo

Un receptor mínimo valida la cabecera secreta, lee el evento y enruta. Devuelve 2xx en cuanto persistas o encoles el trabajo; si tu lógica falla, persiste en background y reconcilia más tarde.

POST /webhooks/factura — receptor

webhook-handler.ts
// webhook-handler.ts (Express)
import type { Request, Response } from "express";
const SECRETO = process.env.FACTURA_WEBHOOK_SECRET!;
export function manejarWebhook(req: Request, res: Response) {
if (req.header("X-Webhook-Secret") !== SECRETO) {
return res.status(401).end();
}
const { Event, Timestamp, data } = req.body;
switch (Event) {
case "dgii.aprobado":
return procesar(res, data, "aprobado");
case "dgii.rechazado":
return procesar(res, data, "rechazado");
case "dgii.en_proceso":
return procesar(res, data, "en_proceso");
case "dgii.aceptado_condicional":
return procesar(res, data, "aceptado_condicional");
default:
return res.status(204).end();
}
}
function procesar(res: Response, data: unknown, estado: string) {
return res.status(200).json({ estado });
}
webhook_handler.py
# webhook_handler.py (FastAPI)
import os
from fastapi import FastAPI, Header, HTTPException, Request
app = FastAPI()
SECRETO = os.environ["FACTURA_WEBHOOK_SECRET"]
@app.post("/webhooks/factura")
async def recibir(req: Request, x_webhook_secret: str = Header(default="")):
if x_webhook_secret != SECRETO:
raise HTTPException(status_code=401)
body = await req.json()
event = body["Event"]
if event == "dgii.aprobado":
# persiste en tu ERP
return {"estado": "aprobado"}
if event == "dgii.rechazado":
return {"estado": "rechazado"}
return {}

Reconciliación y eventos de plugins

Como la entrega no se reintenta, un evento perdido no vuelve a llegar. Estas rutas permiten recuperar lo que no llegó barriendo un rango de fechas: accounting-sync devuelve la misma proyección contable del webhook y treasury-sync, la cartera (qué se debe y a quién). Aceptan la X-Api-Key. plugins/events es distinto: solo lo usan los plugins instalados, con su propio token, para publicar eventos hacia otros plugins.

GET /api/v1/documents/accounting-sync X-Api-Key Estable

Devuelve las ventas, notas y gastos emitidos en un rango de fechas con su desglose contable, la misma proyección del webhook. Responde { items, count, hasMore }.

Nombre Tipo Descripción
from query string (ISO 8601) Requerido

Inicio del rango (fecha de emisión).

to query string (ISO 8601) Requerido

Fin del rango. Si es anterior a `from`, la API responde `400`.

documentTypeId query integer Opcional

Limita el barrido a un tipo de documento.

pageSize query integer Opcional

Por defecto 200, máximo 500. Si `hasMore` es `true`, vuelve a pedir desde la última fecha devuelta.

GET /api/v1/documents/treasury-sync X-Api-Key Estable

Devuelve ventas, compras y notas que representan una deuda, con cliente o proveedor y formas de pago. Responde { items, count, nextFrom, nextAfterDocumentId, hasMore }.

Nombre Tipo Descripción
from query string (ISO 8601) Opcional

Cursor de fecha. En la página siguiente, envía el `nextFrom` recibido.

afterDocumentId query string (uuid) Opcional

Segunda parte del cursor. En la página siguiente, envía el `nextAfterDocumentId` recibido.

to query string (ISO 8601) Opcional

Fecha de emisión máxima. Si es anterior a `from`, la API responde `400`.

documentTypeId query integer Opcional

Limita el resultado a un tipo de documento.

pageSize query integer Opcional

Por defecto 200, máximo 500.

Siguientes pasos

Siguiente paso

Continúa por aquí