Factumel — Tu facturación electrónica, más simple.
Eventos asíncronos

Webhooks y reintentos

Recibí notificaciones cuando cambia el estado de los comprobantes. Configurá un endpoint HTTPS y elegí eventos desde Integraciones → Webhooks. El secreto de firma se muestra una sola vez y se puede regenerar.

Eventos recomendados

EventoSignificado
document.createdBorrador creado.
document.signedSe firmó el documento; no equivale a aceptación.
document.sentSe registró el envío/recepción del sobre.
document.acceptedLa respuesta fiscal indica aceptación.
document.observedDGI observó el comprobante.
document.rejectedDGI rechazó el comprobante.
dgi.transport_unknownResultado de transmisión incierto: conciliar antes de reenviar.

Catálogo de eventos disponibles

Además de los eventos resumidos arriba, el backend permite suscribirse a los siguientes eventos según la configuración de la organización. No todos se generan para cada tipo de CFE:

Nombre del eventoSuscripción
document.createdEvento específico, document.* o * donde corresponda
document.number_reservedEvento específico, document.* o * donde corresponda
document.ready_for_xmlEvento específico, document.* o * donde corresponda
document.xml_generatedEvento específico, document.* o * donde corresponda
document.signedEvento específico, document.* o * donde corresponda
document.xsd_validatedEvento específico, document.* o * donde corresponda
document.xsd_rejectedEvento específico, document.* o * donde corresponda
document.xsd_pendingEvento específico, document.* o * donde corresponda
document.xsd_unavailableEvento específico, document.* o * donde corresponda
document.envelopedEvento específico, document.* o * donde corresponda
document.sentEvento específico, document.* o * donde corresponda
document.acceptedEvento específico, document.* o * donde corresponda
document.observedEvento específico, document.* o * donde corresponda
document.rejectedEvento específico, document.* o * donde corresponda
document.cancelledEvento específico, document.* o * donde corresponda
document.number_cancelledEvento específico, document.* o * donde corresponda
dgi.envelope_receivedEvento específico, document.* o * donde corresponda
dgi.envelope_rejectedEvento específico, document.* o * donde corresponda
dgi.envelope_unknown_ackEvento específico, document.* o * donde corresponda
dgi.transport_unknownEvento específico, document.* o * donde corresponda
dgi.soap_faultEvento específico, document.* o * donde corresponda
dgi.cfe_status_checkedEvento específico, document.* o * donde corresponda
contingency.cfc_registeredEvento específico, document.* o * donde corresponda
contingency.cfc_signedEvento específico, document.* o * donde corresponda
contingency.cfc_envelopedEvento específico, document.* o * donde corresponda
interchange.outbound_envelope_createdEvento específico, document.* o * donde corresponda
interchange.outbound_ws_sentEvento específico, document.* o * donde corresponda
interchange.ack_cfe_receivedEvento específico, document.* o * donde corresponda

Formato de entrega real

El webhook contiene id, type, createdAt y data. Los eventos fiscales incluyen data.event y data.document; las pruebas desde el panel generan un evento webhook.test con otro contenido en data.

{
  "id": "evt_<identificador>_document.accepted",
  "type": "document.accepted",
  "createdAt": "2026-10-12T12:00:00.000Z",
  "data": {
    "event": { "sourceType": "dgi.cfe_status_checked", "status": "ACCEPTED",
               "message": null, "payload": {} },
    "document": { "id": "<document-id>", "externalId": "pedido-123",
                  "environment": "TEST", "status": "ACCEPTED",
                  "documentType": "111", "total": "1220.00" }
  }
}

Ejemplo ilustrativo de estructura, no es respuesta certificada por DGI. El webhook es una petición HTTP saliente de Factumel: su contrato está definido en webhooks.fiscalEvent.post dentro de OpenAPI 3.1. La administración de endpoints puede realizarse desde el panel o por API REST con una clave organizacional que tenga el permiso webhooks:manage. Las claves restringidas a una empresa no pueden administrar destinos de toda la organización.

Gestionar webhooks desde la API

El secreto de firma aparece sólo al crear el endpoint y al rotarlo. Un GET nunca lo devuelve. El API permite configurar suscripciones con HTTP JSON y proteger los destinos HTTPS contra SSRF.

OperaciónRuta
Listar / CrearGET /api/v1/webhooks y POST /api/v1/webhooks
Detalle / Actualizar / ArchivarGET/PATCH/DELETE /api/v1/webhooks/{id}
Rotar secretoPOST /api/v1/webhooks/{id}/rotate-secret
curl -sS -X POST https://factumel.com/api/v1/webhooks \
  -H "Authorization: Bearer $FACTUMEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://tu-servidor.uy/factumel","events":["document.accepted","document.rejected"]}'

Estas operaciones requieren el scope webhooks:manage y una API key de alcance organizacional. Desactivar un endpoint detiene nuevas entregas; archivarlo cancela las pendientes. Antes de rotar el secreto, coordiná el cambio con el receptor: durante la transición puede existir una entrega firmada con el secreto anterior.

Firma de cada entrega

El cuerpo llega en JSON. Los headers x-factumel-timestamp y x-factumel-signature permiten validar integridad. La firma es v1=HMAC_SHA256(secret, timestamp + "." + rawBody) en hexadecimal. Verificá con comparación constante y rechazá timestamps antiguos (por ejemplo, más de cinco minutos).

Ejemplo Node.js: verificación segura

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyFactumelWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-factumel-timestamp"];
  const received = headers["x-factumel-signature"];
  if (!timestamp || !/^v1=[0-9a-f]{64}$/.test(received || "")) return false;
  const value = Number(timestamp);
  if (!Number.isSafeInteger(value) ||
      Math.abs(Date.now() / 1000 - value) > 300) return false;
  const expected = createHmac("sha256", secret)
    .update(timestamp + "." + rawBody, "utf8")
    .digest("hex");
  return timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(received.slice(3), "hex"),
  );
}
// rawBody: los bytes originales UTF-8, antes de JSON.parse.

At-least-once e idempotencia

Un mismo evento puede entregarse más de una vez por fallos de red o reintentos. Guardá x-factumel-event-id o id del payload como clave única antes de aplicar el cambio comercial. Respondé HTTP 2xx sólo después de persistir el procesamiento. Los eventos pueden llegar en distinto orden: consultá el recurso para resolver estados dudosos.

Respondé 2xx sólo después de guardar el evento y el resultado de negocio. Comprobá x-factumel-event-id frente al id del JSON, rechazá discrepancias, almacená IDs procesados con una restricción UNIQUE y nunca aceptes una firma sobre un JSON reserializado.

El dispatcher aplica reintentos con límites y administra entregas fallidas desde el panel. Los destinos se validan contra SSRF, direcciones privadas y protocolos no permitidos. Para integraciones de terceros usá HTTPS público y no expongas el secreto.