28 events by subscription and zero assumptions
You configure the URL from the app, choose the events and optionally add custom headers to authenticate your receiver. The payload shape is standard and the event list is seeded from the initial migration.
Summary
When an event occurs within the workspace, factura.com.do sends a POST with a JSON body to the URL you registered. Delivery is fire-and-forget: your receiver responds 2xx and the delivery is persisted in the webhook execution log. If it responds differently, there is no automatic retry.
Subscription model
The subscription is managed from the Webhooks section of the workspace, not through a public API. You configure the destination URL, HTTP method (GET/POST/PUT), custom headers (name-value) and event selection. The app exposes an internal test endpoint to verify connectivity before activating.
Payload shape
All events share the same body shape: Event, the event name; Timestamp, ISO 8601 in the server's local time (with its offset, not normalized to UTC); and event-specific data. Keys arrive in PascalCase.
{ "Event": "dgii.aprobado", "Timestamp": "2026-05-08T10:33:21.4830000-04:00", "data": { "DocumentId": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "StatusId": 5 }}Catalog of 28 events
The 28 events cover quotations, invoices, notes, expenses, DGII statuses and catalog. All are seeded from the initial migration; subscribe only to the ones your integration needs.
cotizacion.created Quotations A quotation was created in the workspace.
cotizacion.updated Quotations A quotation was edited.
cotizacion.anulated Quotations A quotation was voided.
factura.created Invoices An invoice (E31, E32, E44, E45 or E46) was created.
factura.updated Invoices An existing invoice was modified and saved from the app. edit-amount and edit-text do not fire it: they issue a new note.
factura.anulated Invoices An invoice was voided.
nota_credito.created Credit notes A credit note (E34) was issued, including the one edit-amount and edit-text generate on an invoice.
nota_credito.updated Credit notes An existing credit note was modified.
nota_credito.anulated Credit notes A credit note was voided.
nota_debito.created Debit notes A debit note (E33) was issued, including the one edit-amount and edit-text generate on an expense, purchase or foreign payment.
nota_debito.updated Debit notes An existing debit note was modified.
nota_debito.anulated Debit notes A debit note was voided.
gasto.created Expenses A minor expense (E43), purchase (E41) or foreign payment (E47) was registered.
gasto.updated Expenses An existing expense was modified.
gasto.anulated Expenses An expense was voided.
dgii.aprobado DGII status The DGII accepted the document in full (StatusId 5).
dgii.rechazado DGII status The DGII rejected the document (StatusId 6). Reconcile with a GET to the resource to read the persisted reason.
dgii.en_proceso DGII status The DGII is still evaluating the document (StatusId 7). Polling continues.
dgii.aceptado_condicional DGII status The DGII accepted with observations (StatusId 8). Log the entry.
customer.created Contacts A customer was registered in the workspace.
customer.updated Contacts A customer was updated.
customer.deactivated Contacts A customer was deactivated.
product.created Catalog A product was added to the catalog.
product.updated Catalog A product was updated.
product.deactivated Catalog A product was deactivated.
supplier.created Contacts A supplier was registered.
supplier.updated Contacts A supplier was updated.
supplier.deactivated Contacts A supplier was deactivated.
The data shape per event
The envelope (<code>Event</code>, <code>Timestamp</code>, <code>data</code>) is invariant. What changes is the content of <code>data</code>. These are the fields the dispatcher emits; ignore the ones you do not use, since new fields may appear.
| Events | Fields in 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 |
Each Lines item carries LineId, ProductId, ProductVariantId, WarehouseId, Code, Order, Description, IsService, IsExempt, Quantity, UnitId, UnitPrice, Discount, DiscountPercentage, Recharge, NetAmount, TotalDiscount, TotalTax, TotalWithholdings, Subtotal, Taxes and Withholdings. Accounting is the header accounting breakdown: DocumentTypeId, BranchId, SequenceTypeId, IssuedAt, LimitDate, PaymentTermId, CurrencyCode, CurrencyRate, TaxableAmount, ExemptAmount, TotalDiscount, DiscountPercentage, TotalTaxes, TotalWithholdings, Total, AmountDue, Freight, Tip, TratamientoItbis, TipoBienServicioComprado and AwaitsFiscalValidation.
Example receiver
A minimal receiver validates the secret header, reads the event and routes it. Return 2xx as soon as you persist or enqueue the work; if your logic fails, persist in the background and reconcile later.
POST /webhooks/factura — receiver
// 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 (FastAPI)import osfrom 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 {}Reconciliation and plugin events
Because delivery is not retried, a lost event never arrives again. These routes let you recover what was missed by sweeping a date range: accounting-sync returns the same accounting projection as the webhook and treasury-sync, the receivables and payables (what is owed and by whom). They accept the X-Api-Key. plugins/events is different: only installed plugins use it, with their own token, to publish events to other plugins.
/api/v1/documents/accounting-sync X-Api-Key Stable 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 }.
| Name | Type | Description |
|---|---|---|
from query | string (ISO 8601) Required | Inicio del rango (fecha de emisión). |
to query | string (ISO 8601) Required | Fin del rango. Si es anterior a `from`, la API responde `400`. |
documentTypeId query | integer Optional | Limita el barrido a un tipo de documento. |
pageSize query | integer Optional | Por defecto 200, máximo 500. Si `hasMore` es `true`, vuelve a pedir desde la última fecha devuelta. |
/api/v1/documents/treasury-sync X-Api-Key Stable Devuelve ventas, compras y notas que representan una deuda, con cliente o proveedor y formas de pago. Responde { items, count, nextFrom, nextAfterDocumentId, hasMore }.
| Name | Type | Description |
|---|---|---|
from query | string (ISO 8601) Optional | Cursor de fecha. En la página siguiente, envía el `nextFrom` recibido. |
afterDocumentId query | string (uuid) Optional | Segunda parte del cursor. En la página siguiente, envía el `nextAfterDocumentId` recibido. |
to query | string (ISO 8601) Optional | Fecha de emisión máxima. Si es anterior a `from`, la API responde `400`. |
documentTypeId query | integer Optional | Limita el resultado a un tipo de documento. |
pageSize query | integer Optional | Por defecto 200, máximo 500. |
/api/v1/plugins/events Stable Un plugin instalado publica un evento propio y el host lo reparte a los demás plugins suscritos. Solo acepta el token del plugin; con X-Api-Key responde 403.
| Name | Type | Description |
|---|---|---|
eventType body | string Required | Uno de `cobro.registrado`, `pago_proveedor.registrado` o `inventario.movimiento`. Cualquier otro responde `400`. |
data body | object Optional | Contenido del evento. El host no lo interpreta: lo reenvía tal cual. |