---
title: "Webhooks"
description: "28 eventos por suscripción: configura URL, cabeceras y selección de eventos. Forma del payload, receptor de ejemplo y caveats sobre HMAC y reintentos."
canonical: https://factura.com.do/desarrolladores/webhooks
lang: es
generator: factura.com.do docs-index
---

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.

Solo en el plan Corporativo

Los webhooks salientes son exclusivos del plan Corporativo. Si el espacio de trabajo tiene otro plan, no se despacha ningún evento, aunque la suscripción esté activa, y no queda aviso en ninguna parte.

## 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`.

Hoy no firmamos el payload con HMAC

Si tu auditoría requiere autenticar la entrega, configura una cabecera personalizada (por ejemplo, `X-Webhook-Secret` con un valor compartido) al registrar la suscripción y valida ese valor en tu receptor. Usa HTTPS siempre.

La entrega es fire-and-forget

Si tu receptor responde distinto de 2xx, no hay reintento automático del lado del servidor. Trata cada evento como at-most-once y reconcilia con un GET puntual al recurso si el estado importa para tu flujo financiero.

## 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

TypeScript Python 

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.                                                                 |

[  POST /api/v1/plugins/events Estable 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. ](#) 

| Nombre         | Tipo             | Descripción                                                                                                               |
| -------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| eventType body | string Requerido | Uno de \`cobro.registrado\`, \`pago\_proveedor.registrado\` o \`inventario.movimiento\`. Cualquier otro responde \`400\`. |
| data body      | object Opcional  | Contenido del evento. El host no lo interpreta: lo reenvía tal cual.                                                      |

## Siguientes pasos

Siguiente paso 

## Continúa por aquí

- [  Ciclo del e-CF Cómo se origina cada evento dgii.\* en el flujo de envío y polling. ](/desarrolladores/ciclo-ecf)
- [  Errores Códigos HTTP y forma estándar de respuestas problemáticas. ](/desarrolladores/errores)
- [  Facturas Recurso emisor de los eventos factura.\* y dgii.\*. ](/desarrolladores/facturas)
