Saltar al contenido
API REST v1

Factura electrónica desde tu sistema, con una sola llamada

La API de Nodiq e-CF crea, firma y envía tus comprobantes a la DGII, te devuelve el resultado y te avisa por webhook cuando algo cambia. Todo en JSON, con ejemplos en cURL, PHP y Node.js.

Primeros pasos y autenticación

La URL base de la API es https://ecf.nodiqtech.com/api/v1. Todas las peticiones van por HTTPS, con cuerpo y respuesta en JSON (UTF-8).

  1. Crea tu cuenta y registra tu empresa.
  2. En API y webhooks, dentro del panel, crea una clave. Se muestra completa una sola vez: guárdala en un lugar seguro.
  3. Envía la clave en la cabecera Authorization de cada petición.
Authorizationcabecera · obligatorioBearer nq_test_… para práctica y pruebas, Bearer nq_live_… para producción.
Content-Typecabeceraapplication/json en las peticiones con cuerpo.
Idempotency-KeycabeceraTexto único (hasta 100 caracteres) para repetir un POST sin duplicar. Ver idempotencia.
Nunca uses la clave desde el navegador ni la publiques en una app móvil: cualquiera podría emitir a tu nombre. Úsala solo desde tu servidor y revócala si se expone.
Comprobar la clave
curl https://ecf.nodiqtech.com/api/v1/estado \
  -H "Authorization: Bearer nq_test_xxxxxxxxxxxxxxxx"
$ch = curl_init('https://ecf.nodiqtech.com/api/v1/estado');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NODIQ_CLAVE')],
    CURLOPT_RETURNTRANSFER => true,
]);
$estado = json_decode(curl_exec($ch), true);
const r = await fetch('https://ecf.nodiqtech.com/api/v1/estado', {
  headers: { Authorization: `Bearer ${process.env.NODIQ_CLAVE}` },
});
const estado = await r.json();

Ambientes y modo prueba

El ambiente lo decide la clave, no la URL:

nq_test_…clave de pruebaEmite en el ambiente de práctica (simulado, sin conexión con la DGII) o de pre-certificación de la DGII, según la etapa de tu empresa. Los comprobantes no tienen valor fiscal y no cuentan para tu plan.
nq_live_…clave de producciónEmite comprobantes reales ante la DGII. Requiere que tu empresa esté autorizada como emisor electrónico y tenga certificado y secuencias vigentes.

Cada comprobante indica su ambiente en el campo ambiente: Simulado, TesteCF, CerteCF o eCF (producción).

Fragmento de respuestaJSON
{
  "id": "01JBZ8Q3W6Y7N2K4R5T8V9X0AB",
  "encf": "E320000000001",
  "ambiente": "Simulado",
  "estado": "aceptado"
}

Idempotencia

Las redes fallan. Si tu sistema reintenta un POST porque no recibió respuesta, envía la misma cabecera Idempotency-Key: te devolveremos el comprobante que ya se creó en lugar de emitir otro y gastar otro e-NCF.

  • Usa un valor que identifique la operación en tu sistema, por ejemplo pedido-1001.
  • La clave es única por empresa. Si la reutilizas con un cuerpo distinto, recibirás 409 con el código idempotencia.
  • También puedes enviar referencia_externa para buscar luego el comprobante por tu propio identificador.
Reintento segurocURL
curl -X POST https://ecf.nodiqtech.com/api/v1/comprobantes \
  -H "Authorization: Bearer nq_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1001" \
  -d @comprobante.json

# Repetir la misma petición devuelve el mismo
# comprobante (mismo id y mismo e-NCF).

Errores

Los errores usan códigos HTTP estándar y siempre el mismo formato. mensaje está redactado para mostrarlo a una persona; detalles señala cada campo con problema.

400Petición mal formada (JSON inválido).
401Falta la clave, es inválida o fue revocada.
403La clave no tiene permiso para esta acción o la empresa está suspendida.
404El recurso no existe o pertenece a otra empresa.
409Conflicto: idempotencia, estado que no permite la acción o secuencia agotada.
422Error de validación en los datos.
429Se superó el límite de peticiones.
500Error nuestro. El mensaje incluye una referencia para soporte.

Un rechazo de la DGII no es un error HTTP: el comprobante se crea con estado: "rechazado" y los motivos en mensajes_dgii.

422 · Error de validaciónJSON
{
  "error": {
    "codigo": "validacion",
    "mensaje": "Revisa los datos del comprobante.",
    "detalles": [
      { "campo": "comprador.rnc", "mensaje": "El RNC 131000000 no es válido." },
      { "campo": "lineas.0.precio", "mensaje": "El precio debe ser mayor que cero." }
    ]
  }
}

Límites y paginación

Cada clave admite 120 peticiones por minuto. Si las superas recibirás 429: espera unos segundos y reintenta con espera creciente.

Los listados se paginan con pagina (desde 1) y por_pagina (de 1 a 100, 25 por defecto), y devuelven los resultados en datos junto con la información de paginación.

Forma de un listadoJSON
{
  "datos": [ { "id": "01JBZ8…", "encf": "E310000000149" } ],
  "paginacion": { "pagina": 1, "por_pagina": 25, "total": 134, "paginas": 6 }
}

Crear y emitir un comprobante

POST/comprobantes

Valida los datos, calcula impuestos y totales, asigna el siguiente e-NCF de tu secuencia, firma el XML con tu certificado y lo envía a la DGII. La respuesta llega en segundos con el estado; si la DGII tarda, recibirás en_proceso y un webhook cuando se resuelva.

Cuerpo

tipoentero · obligatorioTipo de e-CF: 31, 32, 33, 34, 41, 43, 44, 45, 46 o 47. Ver catálogos.
fecha_emisionfechaAAAA-MM-DD. Por defecto, hoy.
cliente_identeroUn cliente guardado. Alternativa a comprador.
compradorobjetoObligatorio en 31, 33, 34, 41, 44, 45 y en 32 de RD$250,000 o más. Campos: rnc (RNC o cédula) o extranjero (identificador), razon_social, correo, direccion, municipio, provincia (códigos DGII), pais.
lineasarreglo · obligatorioDe 1 a 1,000 líneas. Ver abajo.
tipo_pagoentero · obligatorio1 contado, 2 crédito, 3 gratuito.
fecha_limite_pagofechaObligatoria si tipo_pago es 2.
formas_pagoarreglo[{"forma": 1, "monto": 1180.00}]. Códigos en catálogos.
monedatextoCódigo ISO (DOP por defecto, USD, EUR…).
tasa_cambiodecimalObligatoria si la moneda no es DOP.
tipo_ingresostexto01 a 06. Por defecto 01 (operaciones).
precios_con_itbisbooleanoSi es true, los precios de las líneas ya incluyen el ITBIS y se desglosa.
referenciaobjetoObligatoria en 33 y 34: encf modificado, fecha, codigo de modificación (1 a 5) y razon.
referencia_externatextoTu identificador (hasta 100 caracteres).
notastextoNota visible en la representación impresa.

Cada línea

nombretexto · obligatorioHasta 80 caracteres.
descripciontextoHasta 1,000 caracteres.
cantidaddecimal · obligatorioMayor que cero.
preciodecimal · obligatorioPrecio unitario (sin ITBIS, salvo precios_con_itbis).
indicadorentero · obligatorio0 no facturable, 1 ITBIS 18 %, 2 ITBIS 16 %, 3 ITBIS 0 %, 4 exento.
tipoentero · obligatorio1 bien, 2 servicio.
descuentodecimalMonto de descuento de la línea.
codigotextoTu código de producto (hasta 35 caracteres).
unidadenteroCódigo de unidad de medida de la DGII (por ejemplo, 43 = unidad).
retencion_itbis_pctdecimalPorcentaje de ITBIS retenido por el comprador (por ejemplo, 30 o 100).
retencion_isr_pctdecimalPorcentaje de ISR retenido (por ejemplo, 2, 10 o 27).

Parámetros de consulta

validarbooleano?validar=true hace una prueba en seco: valida y calcula sin consumir e-NCF ni enviar a la DGII. Ver validar.
Factura de crédito fiscal (31)
curl -X POST https://ecf.nodiqtech.com/api/v1/comprobantes \
  -H "Authorization: Bearer nq_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1001" \
  -d '{
    "tipo": 31,
    "comprador": {
      "rnc": "101010101",
      "razon_social": "Ferretería Ozama, SRL",
      "correo": "compras@ozama.com.do"
    },
    "tipo_pago": 2,
    "fecha_limite_pago": "2026-12-05",
    "lineas": [
      { "nombre": "Cemento gris 42.5 kg", "cantidad": 40,
        "precio": 545.00, "indicador": 1, "tipo": 1, "unidad": 43 },
      { "nombre": "Transporte", "cantidad": 1,
        "precio": 2500.00, "indicador": 1, "tipo": 2 }
    ],
    "referencia_externa": "PED-1001"
  }'
$cuerpo = [
    'tipo' => 31,
    'comprador' => [
        'rnc' => '101010101',
        'razon_social' => 'Ferretería Ozama, SRL',
        'correo' => 'compras@ozama.com.do',
    ],
    'tipo_pago' => 2,
    'fecha_limite_pago' => '2026-12-05',
    'lineas' => [
        ['nombre' => 'Cemento gris 42.5 kg', 'cantidad' => 40, 'precio' => 545.00,
         'indicador' => 1, 'tipo' => 1, 'unidad' => 43],
        ['nombre' => 'Transporte', 'cantidad' => 1, 'precio' => 2500.00,
         'indicador' => 1, 'tipo' => 2],
    ],
    'referencia_externa' => 'PED-1001',
];
$ch = curl_init('https://ecf.nodiqtech.com/api/v1/comprobantes');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('NODIQ_CLAVE'),
        'Content-Type: application/json',
        'Idempotency-Key: pedido-1001',
    ],
    CURLOPT_POSTFIELDS => json_encode($cuerpo),
]);
$r = json_decode(curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($http >= 400) {
    throw new RuntimeException($r['error']['mensaje']);
}
echo $r['encf'], ' ', $r['estado']; // E310000000149 aceptado
const r = await fetch('https://ecf.nodiqtech.com/api/v1/comprobantes', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NODIQ_CLAVE}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'pedido-1001',
  },
  body: JSON.stringify({
    tipo: 31,
    comprador: {
      rnc: '101010101',
      razon_social: 'Ferretería Ozama, SRL',
      correo: 'compras@ozama.com.do',
    },
    tipo_pago: 2,
    fecha_limite_pago: '2026-12-05',
    lineas: [
      { nombre: 'Cemento gris 42.5 kg', cantidad: 40, precio: 545,
        indicador: 1, tipo: 1, unidad: 43 },
      { nombre: 'Transporte', cantidad: 1, precio: 2500,
        indicador: 1, tipo: 2 },
    ],
    referencia_externa: 'PED-1001',
  }),
});
const doc = await r.json();
if (!r.ok) throw new Error(doc.error.mensaje);
console.log(doc.encf, doc.estado); // E310000000149 aceptado
201 · RespuestaJSON
{
  "id": "01JBZ8Q3W6Y7N2K4R5T8V9X0AB",
  "encf": "E310000000149",
  "tipo": 31,
  "estado": "aceptado",
  "ambiente": "TesteCF",
  "fecha_emision": "2026-11-05",
  "comprador": { "rnc": "101010101", "razon_social": "Ferretería Ozama, SRL" },
  "moneda": "DOP",
  "totales": {
    "gravado": 24300.00, "exento": 0.00, "itbis": 4374.00,
    "total": 28674.00, "itbis_retenido": 0.00, "isr_retenido": 0.00
  },
  "codigo_seguridad": "a7Xk2P",
  "fecha_firma": "2026-11-05 10:42:18",
  "track_id": "4f3c9a1e-…",
  "mensajes_dgii": [],
  "url_pdf": "https://ecf.nodiqtech.com/c/9f2b…/pdf",
  "url_xml": "https://ecf.nodiqtech.com/c/9f2b…/xml",
  "url_publica": "https://ecf.nodiqtech.com/c/9f2b…",
  "url_qr": "https://ecf.dgii.gov.do/testecf/consultatimbre?…",
  "creado": "2026-11-05T10:42:17-04:00"
}

Validar en seco

POST/comprobantes?validar=true

Recibe el mismo cuerpo que la creación, aplica todas las validaciones (incluido el esquema XSD de la DGII) y devuelve los totales calculados, sin asignar e-NCF, sin firmar y sin enviar. Úsalo para probar tu integración o para mostrar el total antes de confirmar una venta.

Si hay errores, responde 422 igual que la creación.

200 · RespuestaJSON
{
  "valido": true,
  "totales": {
    "gravado": 24300.00, "exento": 0.00, "itbis": 4374.00,
    "total": 28674.00, "itbis_retenido": 0.00, "isr_retenido": 0.00
  },
  "advertencias": []
}

Listar comprobantes

GET/comprobantes
estadotextoFiltra por estado (ver objeto comprobante).
tipoenteroFiltra por tipo de e-CF.
desdefechaFecha de emisión inicial (incluida).
hastafechaFecha de emisión final (incluida).
paginaenteroDesde 1.
por_paginaenteroDe 1 a 100. Por defecto 25.

Ordenado del más reciente al más antiguo.

Rechazados de noviembre
curl "https://ecf.nodiqtech.com/api/v1/comprobantes?estado=rechazado&desde=2026-11-01&hasta=2026-11-30" \
  -H "Authorization: Bearer nq_test_…"
$q = http_build_query(['estado' => 'rechazado', 'desde' => '2026-11-01', 'hasta' => '2026-11-30']);
$ch = curl_init("https://ecf.nodiqtech.com/api/v1/comprobantes?$q");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NODIQ_CLAVE')],
    CURLOPT_RETURNTRANSFER => true,
]);
$lista = json_decode(curl_exec($ch), true)['datos'];
const q = new URLSearchParams({ estado: 'rechazado', desde: '2026-11-01', hasta: '2026-11-30' });
const r = await fetch(`https://ecf.nodiqtech.com/api/v1/comprobantes?${q}`, {
  headers: { Authorization: `Bearer ${process.env.NODIQ_CLAVE}` },
});
const { datos, paginacion } = await r.json();

Obtener un comprobante

GET/comprobantes/{id}

{id} es el identificador que devolvió la creación (no el e-NCF). Devuelve el objeto comprobante completo, con sus líneas.

Si recibiste en_proceso, puedes consultar aquí cada pocos segundos, aunque es mejor esperar el webhook documento.aceptado o documento.rechazado.

ConsultarcURL
curl https://ecf.nodiqtech.com/api/v1/comprobantes/01JBZ8Q3W6Y7N2K4R5T8V9X0AB \
  -H "Authorization: Bearer nq_test_…"

Descargar PDF y XML

GET/comprobantes/{id}/pdf
GET/comprobantes/{id}/xml

El PDF es la representación impresa con el código QR y el código de seguridad (application/pdf). El XML es el documento firmado tal como se envió a la DGII (application/xml). El nombre del archivo es el RNC del emisor seguido del e-NCF.

Para compartir con tu cliente sin exponer tu clave, usa url_publica, url_pdf o url_xml del objeto comprobante: son enlaces públicos y difíciles de adivinar.

Guardar el PDF
curl https://ecf.nodiqtech.com/api/v1/comprobantes/01JBZ8Q3W6Y7N2K4R5T8V9X0AB/pdf \
  -H "Authorization: Bearer nq_test_…" \
  -o 101010101E310000000149.pdf
$ch = curl_init('https://ecf.nodiqtech.com/api/v1/comprobantes/' . $id . '/pdf');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NODIQ_CLAVE')],
    CURLOPT_RETURNTRANSFER => true,
]);
file_put_contents('comprobante.pdf', curl_exec($ch));
import { writeFile } from 'node:fs/promises';
const r = await fetch(`https://ecf.nodiqtech.com/api/v1/comprobantes/${id}/pdf`, {
  headers: { Authorization: `Bearer ${process.env.NODIQ_CLAVE}` },
});
await writeFile('comprobante.pdf', Buffer.from(await r.arrayBuffer()));

Reenviar a la DGII

POST/comprobantes/{id}/reenviar

Fuerza un nuevo intento de envío de un comprobante en contingencia o en_proceso, o un nuevo intento de firma de uno en error. No genera un e-NCF nuevo. No es necesario llamarlo: la plataforma reintenta sola.

Para corregir un comprobante rechazado emite uno nuevo; para anular uno aceptado, emite una nota de crédito (34) con referencia.codigo = 1.

ReintentarcURL
curl -X POST https://ecf.nodiqtech.com/api/v1/comprobantes/01JBZ8Q3W6Y7N2K4R5T8V9X0AB/reenviar \
  -H "Authorization: Bearer nq_test_…"

Enviar por correo

POST/comprobantes/{id}/correo
paratexto · obligatorioUno o varios correos separados por coma.

Envía al comprador un correo con el PDF y el XML adjuntos y el enlace público. Úsalo para reenviarlo o para mandarlo a otra dirección.

EnviarcURL
curl -X POST https://ecf.nodiqtech.com/api/v1/comprobantes/01JBZ8Q3W6Y7N2K4R5T8V9X0AB/correo \
  -H "Authorization: Bearer nq_test_…" \
  -H "Content-Type: application/json" \
  -d '{"para": "compras@ozama.com.do"}'

El objeto comprobante

idtextoIdentificador único (26 caracteres).
encftextoe-NCF asignado (13 caracteres).
tipoenteroTipo de e-CF.
estadotextoaceptado, aceptado_condicional, rechazado, en_proceso, contingencia, firmado, error o anulado.
ambientetextoSimulado, TesteCF, CerteCF o eCF.
fecha_emisionfecha
compradorobjetoDatos del comprador tal como se emitieron.
monedatextoCódigo ISO.
totalesobjetogravado, exento, itbis, total, itbis_retenido, isr_retenido.
lineasarregloDetalle con monto e ITBIS por línea.
codigo_seguridadtexto6 caracteres de la firma; aparece en la representación impresa.
fecha_firmafecha y hora
track_idtextoIdentificador de la DGII para el envío.
mensajes_dgiiarregloObservaciones o motivos de rechazo de la DGII.
url_pdfURLRepresentación impresa pública.
url_xmlURLXML firmado público.
url_publicaURLPágina del comprobante para tu cliente.
url_qrURLConsulta del timbre en la DGII (contenido del código QR).
creadofecha y horaISO 8601.
AceptadoVálido. La DGII lo aceptó.
Aceptado condicionalVálido, con observaciones menores de la DGII.
En procesoEnviado; esperando la respuesta de la DGII.
ContingenciaFirmado y válido; la DGII no respondió y se reenvía solo.
RechazadoNo válido. Corrige y emite uno nuevo.
ErrorNo se pudo firmar (por ejemplo, certificado vencido). No consumió validez fiscal.
Nota de crédito (34) que anula una facturaJSON
{
  "tipo": 34,
  "comprador": { "rnc": "101010101", "razon_social": "Ferretería Ozama, SRL" },
  "tipo_pago": 1,
  "lineas": [
    { "nombre": "Devolución total", "cantidad": 1,
      "precio": 24300.00, "indicador": 1, "tipo": 1 }
  ],
  "referencia": {
    "encf": "E310000000149",
    "fecha": "2026-11-05",
    "codigo": 1,
    "razon": "Devolución de mercancía"
  }
}

Catálogos y códigos

Tipos de e-CF

31Factura de Crédito Fiscal Electrónica
32Factura de Consumo Electrónica
33Nota de Débito Electrónica
34Nota de Crédito Electrónica
41Compras Electrónico
43Gastos Menores Electrónico
44Regímenes Especiales Electrónico
45Gubernamental Electrónico
46Comprobante para Exportaciones Electrónico
47Comprobante para Pagos al Exterior Electrónico

Formas de pago

1Efectivo
2Cheque, transferencia o depósito
3Tarjeta de débito o crédito
4Venta a crédito
5Bonos o certificados de regalo
6Permuta
7Nota de crédito
8Otras formas de pago

Códigos de modificación (referencia)

1Anula el comprobante modificado
2Corrige texto
3Corrige montos
4Reemplazo de NCF emitido en contingencia
5Referencia a factura de consumo electrónica

Tipo de ingresos

01Ingresos por operaciones (no financieros)
02Ingresos financieros
03Ingresos extraordinarios
04Ingresos por arrendamientos
05Ingresos por venta de activo depreciable
06Otros ingresos

Facturas de consumo y RFCE

Las facturas de consumo (32) menores de RD$250,000 se informan a la DGII mediante el resumen (RFCE) de forma automática. Para ti no cambia nada: llamas a la API igual y recibes el mismo objeto, con rfce: true.

Comprobantes recibidos

GET/recibidos
GET/recibidos/{id}

Los e-CF que tus proveedores te envían a tu dirección de recepción. Al recibirlos validamos la firma y los datos y enviamos el acuse de recibo automáticamente. Filtros: aprobacion (pendiente, aprobado, rechazado), desde, hasta, rnc_emisor, pagina, por_pagina.

Escucha el webhook recibido.nuevo para enterarte al instante.

Pendientes de aprobarcURL
curl "https://ecf.nodiqtech.com/api/v1/recibidos?aprobacion=pendiente" \
  -H "Authorization: Bearer nq_live_…"
Un recibidoJSON
{
  "id": 8812,
  "encf": "E310000004410",
  "tipo": 31,
  "emisor": { "rnc": "130000001", "razon_social": "Suplidora Caribe, SRL" },
  "fecha_emision": "2026-11-04",
  "totales": { "itbis": 1620.00, "total": 10620.00 },
  "aprobacion": "pendiente",
  "url_xml": "https://ecf.nodiqtech.com/api/v1/recibidos/8812?formato=xml",
  "recibido": "2026-11-04T16:21:09-04:00"
}

Aprobar o rechazar

POST/recibidos/{id}/aprobar
POST/recibidos/{id}/rechazar
motivotextoObligatorio al rechazar (hasta 250 caracteres).

Envía la aprobación comercial al emisor y a la DGII. Solo se puede responder una vez por comprobante.

Rechazar
curl -X POST https://ecf.nodiqtech.com/api/v1/recibidos/8812/rechazar \
  -H "Authorization: Bearer nq_live_…" \
  -H "Content-Type: application/json" \
  -d '{"motivo": "La mercancía no fue entregada."}'
await fetch('https://ecf.nodiqtech.com/api/v1/recibidos/8812/rechazar', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NODIQ_CLAVE}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ motivo: 'La mercancía no fue entregada.' }),
});

Clientes

GET/clientes
POST/clientes

Guarda a tus compradores para emitir luego con cliente_id. El listado acepta q (busca por nombre o RNC), pagina y por_pagina. Al crear se aceptan los mismos campos que comprador; si envías un RNC, completamos la razón social desde el padrón de la DGII.

Crear clientecURL
curl -X POST https://ecf.nodiqtech.com/api/v1/clientes \
  -H "Authorization: Bearer nq_test_…" \
  -H "Content-Type: application/json" \
  -d '{"rnc": "101010101", "correo": "compras@ozama.com.do"}'

Secuencias

GET/secuencias

Los rangos de e-NCF autorizados por la DGII para tu empresa, por tipo: desde, hasta, siguiente número, disponibles y fecha de vencimiento. Recibirás el webhook secuencia.por_agotarse cuando queden pocas.

RespuestaJSON
{
  "datos": [
    { "tipo": 31, "desde": 1, "hasta": 5000, "siguiente": 150,
      "disponibles": 4851, "vence": "2027-12-31", "activa": true }
  ]
}

Consultar un contribuyente

GET/contribuyentes/{rnc}

Busca un RNC o cédula en el padrón de contribuyentes de la DGII y te dice si es emisor electrónico (para saber si debes enviarle el e-CF por la vía electrónica).

RespuestaJSON
{
  "rnc": "101010101",
  "razon_social": "FERRETERIA OZAMA SRL",
  "nombre_comercial": "FERRETERIA OZAMA",
  "estado": "ACTIVO",
  "emisor_electronico": true
}

Estado

GET/estado

Comprueba tu clave y la salud del servicio: ambiente de la clave, empresa, y estado de la plataforma y de la DGII. El mismo estado es público, sin clave, en /estado.json.

RespuestaJSON
{
  "ok": true,
  "empresa": { "rnc": "131000001", "razon_social": "TU EMPRESA SRL" },
  "ambiente": "TesteCF",
  "dgii": "operativo"
}

Webhooks

Registra una URL HTTPS en API y webhooks y elige los eventos. Te enviaremos un POST con JSON cada vez que ocurra uno. Disponibles en todos los planes.

documento.creadoSe creó y firmó un comprobante.
documento.aceptadoLa DGII lo aceptó.
documento.aceptado_condicionalLa DGII lo aceptó con observaciones.
documento.rechazadoLa DGII lo rechazó; los motivos van en mensajes_dgii.
documento.anuladoSe anuló el comprobante o su secuencia.
documento.aprobacion_comercialTu cliente aprobó o rechazó comercialmente tu comprobante.
recibido.nuevoLlegó un e-CF de un proveedor.
secuencia.por_agotarseQuedan pocos e-NCF de un tipo o la secuencia está por vencer.
certificado.por_vencerTu certificado digital vence en menos de 30 días.

Cabeceras

Nodiq-EventocabeceraNombre del evento, por ejemplo documento.aceptado.
Nodiq-EntregacabeceraIdentificador único de la entrega. Úsalo para ignorar duplicados.
Nodiq-Firmacabecerat=<unix>,v1=<hex>. Ver verificar la firma.

Entrega y reintentos

Responde con un código 2xx en menos de 10 segundos (cortamos la conexión a los 15); procesa el trabajo pesado después. Si no, reintentamos 9 veces con espera creciente durante unas 72 horas. Puedes ver cada entrega y reintentarla desde el panel. Los eventos pueden llegar más de una vez o fuera de orden: usa Nodiq-Entrega y el estado del objeto.

Petición que recibesHTTP
POST /webhooks/nodiq HTTP/1.1
Content-Type: application/json
Nodiq-Evento: documento.aceptado
Nodiq-Entrega: 48213
Nodiq-Firma: t=1793896938,v1=5f1c0e…9ab2

{
  "id": "evt_01JBZ9A1C2D3E4F5G6H7J8K9LM",
  "evento": "documento.aceptado",
  "creado": "2026-11-05T10:42:19-04:00",
  "datos": {
    "id": "01JBZ8Q3W6Y7N2K4R5T8V9X0AB",
    "encf": "E310000000149",
    "estado": "aceptado",
    "referencia_externa": "PED-1001",
    "totales": { "itbis": 4374.00, "total": 28674.00 }
  }
}

Verificar la firma

Cada webhook va firmado con el secreto que te mostramos al crearlo (empieza por whsec_). Verifícalo siempre antes de confiar en el contenido:

  1. Lee t y v1 de la cabecera Nodiq-Firma.
  2. Calcula HMAC-SHA256(secreto, t + "." + cuerpo) en hexadecimal, usando el cuerpo crudo, sin volver a codificar el JSON.
  3. Compáralo con v1 en tiempo constante.
  4. Rechaza si t difiere de la hora actual en más de 5 minutos, para evitar reenvíos maliciosos.
Verificación
$cuerpo = file_get_contents('php://input');
$firma = $_SERVER['HTTP_NODIQ_FIRMA'] ?? '';
$secreto = getenv('NODIQ_WEBHOOK_SECRETO');

parse_str(str_replace(',', '&', $firma), $p); // ['t' => …, 'v1' => …]
$esperada = hash_hmac('sha256', ($p['t'] ?? '') . '.' . $cuerpo, $secreto);

if (empty($p['v1']) || !hash_equals($esperada, $p['v1'])
    || abs(time() - (int) $p['t']) > 300) {
    http_response_code(400);
    exit('Firma inválida');
}

$evento = json_decode($cuerpo, true);
// Encola el trabajo y responde rápido.
http_response_code(200);
import crypto from 'node:crypto';
import express from 'express';

const app = express();
app.post('/webhooks/nodiq', express.raw({ type: 'application/json' }), (req, res) => {
  const p = Object.fromEntries(
    (req.get('Nodiq-Firma') || '').split(',').map((x) => x.split('=')),
  );
  const esperada = crypto
    .createHmac('sha256', process.env.NODIQ_WEBHOOK_SECRETO)
    .update(`${p.t}.${req.body}`)
    .digest('hex');
  const ok = p.v1 && p.v1.length === esperada.length &&
    crypto.timingSafeEqual(Buffer.from(p.v1), Buffer.from(esperada)) &&
    Math.abs(Date.now() / 1000 - Number(p.t)) <= 300;
  if (!ok) return res.status(400).send('Firma inválida');

  const evento = JSON.parse(req.body);
  res.sendStatus(200); // procesa después
});

Servicios de recepción para la DGII

Para ser emisor electrónico, la DGII te pide declarar las direcciones donde recibirás e-CF de otros emisores y sus aprobaciones comerciales. Con Nodiq e-CF declaras estas direcciones; son compartidas por todos nuestros clientes y la plataforma enruta cada documento por el RNC del comprador:

Recepciónhttps://ecf.nodiqtech.com/fe/recepcion/api/ecf
Aprobación comercialhttps://ecf.nodiqtech.com/fe/aprobacioncomercial/api/ecf
Autenticación (semilla)https://ecf.nodiqtech.com/fe/autenticacion/api/semilla
Validación de certificadohttps://ecf.nodiqtech.com/fe/autenticacion/api/validacioncertificado

Estas direcciones las consumen la DGII y otros emisores siguiendo el protocolo de la DGII (semilla firmada y token); no usan tu clave de API. En el panel, en Puesta en marcha, encontrarás también una variante con un identificador propio de tu empresa por si la necesitas.

Flujo de un documento recibidoTexto
Emisor externo ──► /fe/autenticacion/api/semilla
                ──► /fe/autenticacion/api/validacioncertificado  (token)
                ──► /fe/recepcion/api/ecf  (e-CF firmado)
                         │
                         ├─ valida firma y RNC del comprador
                         ├─ responde el acuse de recibo (ARECF)
                         └─ webhook recibido.nuevo  ──► tu sistema

Tu aprobación ──► POST /api/v1/recibidos/{id}/aprobar
                ──► ACECF al emisor y a la DGII

OpenAPI, llms.txt y soporte

La especificación completa está en /openapi.json (OpenAPI 3.1): impórtala en Postman, Insomnia o tu generador de clientes favorito para crear tu propio SDK. Para asistentes de IA y agentes, hay un resumen en texto en /llms.txt.

¿Dudas con la integración? Escríbenos a soporte@nodiqtech.com o por WhatsApp; te ayudamos a revisar tus peticiones.