TF Fiscal
Documentación

Webhooks

Catálogo de eventos, cabeceras de entrega, firma HMAC-SHA256, payloads por tipo de documento, reintentos y circuit breaker de los webhooks de TF Fiscal.

Modelo de entrega

La plataforma envía los resultados de los documentos y los veredictos de verificación a su servidor, sin necesidad de polling.

  • Una URL de callback por aplicación, configurada en la consola ("Configuración de webhook" en los detalles de la aplicación) o mediante Registrar webhook. Ambas vías escriben la misma configuración y tienen el mismo efecto; prevalece el último guardado, y el formato y las cabeceras de los callbacks nunca dependen de dónde se configuró la URL. Hoy la consola solo ofrece la suscripción a invoice.verify.completed; suscríbase a los demás eventos con eventos al registrar. Los resultados de NF-e, NFC-e, CT-e, DC-e y NFS-e y el veredicto de la verificación de NF-e se entregan todos en esa URL.
  • El token de callback es opcional. Cuando está definido (en la consola o en el registro), cada callback lo envía sin cambios en las cabeceras token y x-token, sea cual sea el tipo de evento; sin token, no se envía ninguna de las dos. La consola solo muestra el valor enmascarado, y allí se puede cambiar o borrar. Solo caracteres ASCII visibles (sin espacios), hasta 256.
  • Entrega al menos una vez (at-least-once). Un reintento tras un timeout puede llegar aunque el intento original ya se haya procesado, y el mismo evento puede distribuirse a varios destinos. Deduplique por el id del evento (cabecera X-Tffiscal-Event-Id y, en los payloads con sobre, event_id): es idéntico en todos los intentos del mismo evento.
  • Cada entrega es un HTTP POST con cuerpo JSON (Content-Type: application/json; charset=utf-8) a la URL registrada.
  • Los documentos de prueba y de producción siguen el mismo contrato, la misma firma y el mismo calendario de reintentos, vea Entornos.

Catálogo de eventos

EventoSe dispara cuandoPayload
invoice.authorizedNF-e autorizada por SEFAZ (cStat 100)Objeto de resultado NF-e, nfeStatus = Autorizada
invoice.rejectedNF-e rechazada por SEFAZObjeto de resultado NF-e, nfeStatus = Negada, motivo en nfeMotivoStatus
invoice.canceledCancelación de la NF-e registrada en SEFAZ (cStat 135)Sobre de evento, data trae invoice_id / chave / protocolo
invoice.cce.registeredCarta de corrección (CC-e) registrada en una NF-eSobre de evento, data trae invoice_id / chave / n_seq. Reservado: el registro de la CC-e es síncrono hoy y este evento todavía no se entrega
invoice.verify.completedLa verificación de NF-e llegó al veredicto final (VALIDATED / REJECTED / VALIDATION_ERROR)Sobre de evento con el veredicto en data
cte.authorizedCT-e autorizado (cStat 100)Objeto de resultado CT-e, cteStatus = Autorizada
cte.rejectedCT-e rechazado por SEFAZ (cStat distinto de 100)Objeto de resultado CT-e, cteStatus = Negada, cteMotivoStatus = cStat - motivo
cte.canceledCancelación del CT-e registrada (cStat 135)Objeto de resultado CT-e, cteStatus = Cancelada
cte.event.registeredCualquier otro evento de CT-e registrado (carta de corrección, comprobante de entrega, entrega fallida, desacuerdo de servicio y sus cancelaciones)Sobre de evento, data trae event_code / n_seq / protocolo
dce.authorizedDC-e autorizada (cStat 100)Objeto de resultado DC-e, dceStatus = Autorizada
dce.rejectedDC-e rechazada por SEFAZ, o la tarea de emisión falló de forma terminal (incluso antes de numerar el documento)Objeto de resultado DC-e, dceStatus = Negada
dce.canceledCancelación de la DC-e registrada (cStat 135 / 136 / 155)Objeto de resultado DC-e, dceStatus = Cancelada
dce.cancel_rejectedSEFAZ rechazó la cancelación de la DC-e, o la tarea de cancelación falló de forma terminalObjeto de resultado DC-e, dceStatus = CancelamentoNegado, el documento sigue autorizado
nfce.authorizedNFC-e autorizada; también se envía cuando una nota offline se autoriza tras la transmisiónObjeto de resultado NFC-e, nfeStatus = Autorizada
nfce.rejectedNFC-e rechazada por la SEFAZ, o falló en el procesamiento de la plataforma; también se envía cuando una nota offline se rechaza en la transmisiónObjeto de resultado NFC-e, nfeStatus = Negada
nfce.canceledCancelación de la NFC-e (incluida la cancelación por sustitución) registradaObjeto de resultado NFC-e, nfeStatus = Cancelada
nfce.cancel_rejectedSEFAZ rechazó la cancelación de la NFC-eObjeto de resultado NFC-e, nfeStatus = CancelamentoNegado, la nota sigue válida
nfce.contingency_issuedNFC-e emitida offline con la SEFAZ caídaObjeto de resultado NFC-e, nfeStatus = ContingenciaPendente
nfse.authorizedNFS-e emitida con éxitoObjeto de resultado NFS-e, nfseStatus = Autorizada
nfse.rejectedNFS-e rechazada por la autoridad, o fallo del lado de la plataformaObjeto de resultado NFS-e, nfseStatus = Negada, motivo en nfseMotivoStatus
nfse.canceledCancelación de la NFS-e completada (incluido análisis aprobado)Objeto de resultado NFS-e, nfseStatus = Cancelada
nfse.cancel_requestedAnálisis de cancelación de la NFS-e aceptadoObjeto de resultado NFS-e, nfseStatus = EmProcessoDeCancelamento
nfse.cancel_deniedLa cancelación de la NFS-e no ocurrió y el documento sigue autorizadoObjeto de resultado NFS-e, nfseStatus = Autorizada, motivo en nfseMotivoStatus
nfse.substitutedNFS-e sustituida por otro documentoObjeto de resultado NFS-e, nfseStatus = Substituida, más nfseChaveSubstituta
nfse.inbound.receivedNFS-e recibida, siendo este emisor receptor / intermediario (requiere suscripción explícita)Notificación de NFS-e recibida
nfse.inbound.canceledNFS-e recibida cancelada (requiere suscripción explícita)Notificación de NFS-e recibida
nfse.inbound.substitutedNFS-e recibida sustituida (requiere suscripción explícita)Notificación de NFS-e recibida
nfse.inbound.manifestedSe registró la manifestación (confirmación / rechazo) de una NFS-e recibida (requiere suscripción explícita)Sobre de evento, resultado en data
nfse.inbound.manifest_failedFalló la tarea de manifestación de una NFS-e recibida; manifieste de nuevo antes del plazo de confirmación tácita (requiere suscripción explícita)Sobre de evento, motivo del fallo en data.motivo
email.sentE-mail de la factura entregado al servicio de envío (requiere suscripción explícita)Sobre de evento, data.status = Enviado
email.failedE-mail de la factura falló definitivamente: espera agotada, intentos agotados, documento que dejó de estar autorizado (requiere suscripción explícita)Sobre de evento, data.status = Falha, motivo en data.motivo_falha

Se usan dos formas de payload, elegidas solo por el tipo de evento (última columna de arriba), nunca por si la URL se configuró en la consola o mediante el endpoint de registro:

  • Objeto de resultado del documento: objeto JSON plano cuyo primer campo es tipo (NF-e / NFC-e / CT-e / DC-e / NFS-e), con orden fijo de campos. Lo usan los resultados de autorización y rechazo de la NF-e, los cinco eventos de la NFC-e (nfce.*) y los resultados de autorización, rechazo y cancelación de CT-e, DC-e y NFS-e. No tiene campo event_id; deduplique por la cabecera X-Tffiscal-Event-Id.
  • Sobre de evento: { "version", "event_id", "event_type", "occurred_at", "data" }. Lo usan invoice.canceled, invoice.cce.registered, invoice.verify.completed, cte.event.registered, nfse.inbound.* y email.sent / email.failed. Los campos dentro de data solo se añaden, nunca se renombran ni se eliminan; un cambio incompatible incrementa version.

Nota: los rechazos de SEFAZ nunca son errores HTTP de la llamada de emisión. Aparecen como estado Negada en la consulta y como evento invoice.rejected / cte.rejected / dce.rejected. Una tarea de CT-e que falla de forma terminal del lado de la plataforma (Falha) no genera callback; use la consulta de CT-e para detectarla.

Cabeceras de la petición

CabeceraSignificado
X-Tffiscal-EventCódigo del evento, por ejemplo invoice.authorized (webhook.verify en la entrega de prueba enviada al guardar una URL en la consola)
X-Tffiscal-Event-IdId del evento, la clave de idempotencia; no cambia entre reintentos y es compartido por todos los destinos del mismo evento
X-Tffiscal-Delivery-IdIdentificador de la entrega; no deduplique por él, use el id del evento
X-Tffiscal-TimestampSegundos Unix, regenerado en cada intento
X-Tffiscal-Signaturehex( HMAC-SHA256( app_secret, timestamp + "." + body ) ), en minúsculas
tokenToken de callback opcional: el valor definido en la consola o enviado en Registrar webhook, devuelto sin cambios. Ausente cuando no hay token definido
x-tokenMismo valor que token, presente siempre que lo esté token; verifique cualquiera de los dos

Verificar la firma

Calcule HMAC-SHA256 sobre la cadena timestamp + "." + rawBody con su app_secret y compárelo en tiempo constante con la cabecera X-Tffiscal-Signature. Use los bytes crudos recibidos: deserializar y volver a serializar el payload cambia el orden de los campos o los espacios y rompe la firma. Rechace entregas con timestamp demasiado antiguo (5 minutos es una tolerancia razonable).

Se recomienda verificar la firma: prueba el origen y que el cuerpo no fue alterado. Si definió un token de callback, también puede comparar la cabecera token con él.

Node.js:

javascript
const crypto = require('node:crypto');
/**
* Verifica una entrega de webhook de TF Fiscal.
* @param {string} secret su app_secret
* @param {string} timestamp valor de la cabecera X-Tffiscal-Timestamp
* @param {Buffer|string} rawBody cuerpo crudo de la petición, sin parsear
* @param {string} signature valor de la cabecera X-Tffiscal-Signature
* @returns {boolean} true cuando la firma es auténtica
*/
function verifyWebhook(secret, timestamp, rawBody, signature) {
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + rawBody, 'utf8')
.digest('hex');
return (
expected.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), Buffer.from(signature, 'utf8'))
);
}

Java:

java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public final class WebhookVerifier {
/**
* Verifica una entrega de webhook de TF Fiscal.
*
* @param secret su app_secret
* @param timestamp valor de la cabecera X-Tffiscal-Timestamp
* @param rawBody cuerpo crudo de la petición, sin parsear
* @param signature valor de la cabecera X-Tffiscal-Signature
* @return true cuando la firma es auténtica
*/
public static boolean verify(String secret, String timestamp, String rawBody, String signature) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] digest = mac.doFinal((timestamp + "." + rawBody).getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder(digest.length * 2);
for (byte b : digest) {
hex.append(String.format("%02x", b));
}
return MessageDigest.isEqual(
hex.toString().getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
return false;
}
}
}

Payload de NF-e

Eventos invoice.authorized e invoice.rejected. Se registran en Registrar webhook; vea NF-e para el flujo de emisión.

Autorizada:

json
{
"tipo": "NF-e",
"empresaId": "1934811222334455",
"nfeId": "NFe-000014553",
"nfeStatus": "Autorizada",
"nfeLinkDanfe": "https://api.v2.tffiscal.com/openapi/files/danfe/35241204893402000113650010000117691017244265?token=MXw3fDEwMXwxNzU4...",
"nfeLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/8801?token=MXw3fDEwMXwxNzU4...",
"nfeNumero": "11769",
"nfeSerie": "10",
"nfeChaveAcesso": "35241204893402000113650010000117691017244265",
"nfeDataEmissao": "2024-12-04T17:44:26Z",
"nfeDataAutorizacao": "2024-12-04T17:44:26Z",
"nfeNumeroProtocolo": "135240002599237"
}

Denegada:

json
{
"tipo": "NF-e",
"empresaId": "1934811222334455",
"nfeId": "NFe-000014553",
"nfeStatus": "Negada",
"nfeMotivoStatus": "778 - Rejeicao: NCM inexistente",
"nfeNumero": "11769",
"nfeSerie": "10",
"nfeChaveAcesso": "35241204893402000113650010000117691017244265",
"nfeDataEmissao": "2024-12-04T17:44:26Z"
}
CampoTipoDescripción
tipostringSiempre NF-e
empresaIdstringIdentificador de la empresa
nfeIdstringEl id enviado en la emisión
nfeStatusstringAutorizada / Negada
nfeMotivoStatusstringMotivo de la denegación: código de estado de SEFAZ + descripción; vacío cuando está autorizada
nfeLinkDanfestringEnlace de descarga del PDF del DANFE (vea Descarga de archivos; utilizable en el momento del callback); vacío cuando está denegada
nfeLinkXmlstringEnlace de descarga del XML autorizado; vacío cuando está denegada
nfeNumerostringNúmero de la factura
nfeSeriestringSerie
nfeChaveAcessostringClave de acceso de 44 dígitos
nfeDataEmissaostringFecha de emisión, ISO-8601 UTC
nfeDataAutorizacaostringFecha de autorización; vacío cuando está denegada
nfeNumeroProtocolostringNúmero de protocolo de autorización; vacío cuando está denegada
nfeDigestValuestringDigest de la firma, no se proporciona por ahora

Los campos sin valor aparecen en el payload como valores vacíos.

Cancelación y carta de corrección de NF-e

invoice.canceled usa el sobre de evento con data trayendo invoice_id (identificador de la factura en la plataforma), chave (clave de acceso de 44 dígitos) y protocolo (protocolo del evento de cancelación). La cancelación en sí se confirma de forma síncrona en Cancelar NF-e y por el estado Cancelada en la consulta.

invoice.cce.registered está reservado en el catálogo (data: invoice_id / chave / n_seq). El endpoint de registro de la CC-e devuelve el protocolo de forma síncrona y por ahora no se entrega ningún callback para él.

Payload de CT-e

Mismo registro y mismas cabeceras que la NF-e; vea CT-e para el flujo de emisión. El objeto de resultado usa tipo = CT-e con orden fijo de campos.

EventoDisparocteStatus
cte.authorizedAutorización 100Autorizada
cte.rejectedRechazo de SEFAZ (cStat distinto de 100)Negada (cteMotivoStatus es cStat - motivo); un fallo terminal de la tarea (Falha) no genera callback, use la API de consulta
cte.canceledCancelación 135Cancelada
cte.event.registeredCualquier otro evento registradoSobre de evento, data trae event_code / n_seq / protocolo

Autorizado:

json
{
"tipo": "CT-e",
"empresaId": "1934811222334455",
"cteId": "CTE-ORD-1",
"cteStatus": "Autorizada",
"cteMotivoStatus": null,
"cteLinkDacte": "https://api.v2.tffiscal.com/openapi/files/dacte/3526...?token=...",
"cteLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/7?token=...",
"cteNumero": "1",
"cteSerie": "1",
"cteChaveAcesso": "3526...",
"cteDataEmissao": "2026-09-06T12:00:00Z",
"cteDataAutorizacao": "2026-09-06T12:00:03Z",
"cteNumeroProtocolo": "135260000000001",
"cteDigestValue": "..."
}
CampoTipoDescripción
tipostringSiempre CT-e
empresaIdstringIdentificador de la empresa
cteIdstringEl id enviado en la emisión
cteStatusstringAutorizada / Negada / Cancelada
cteMotivoStatusstring | nullcStat - motivo cuando está rechazado; null en los demás casos
cteLinkDactestring | nullEnlace de descarga del PDF del DACTE; null cuando está rechazado
cteLinkXmlstring | nullEnlace de descarga del XML autorizado; null cuando está rechazado
cteNumerostringNúmero del documento
cteSeriestringSerie
cteChaveAcessostringClave de acceso de 44 dígitos
cteDataEmissaostringFecha de emisión, ISO-8601 UTC
cteDataAutorizacaostring | nullFecha de autorización; null cuando está rechazado
cteNumeroProtocolostring | nullNúmero de protocolo de autorización; null cuando está rechazado
cteDigestValuestring | nullDigest de la firma del XML autorizado

cteLinkDacte y cteLinkXml son utilizables en cuanto llega el callback de autorización; los enlaces no necesitan cabeceras de firma y redirigen con 302, vea Descarga de archivos.

Evento registrado (cte.event.registered), por ejemplo un comprobante de entrega (110180):

json
{
"version": "1.0",
"event_id": "987654321098765432",
"event_type": "cte.event.registered",
"occurred_at": "2026-09-06T13:00:00Z",
"data": {
"event_code": "110180",
"n_seq": "1",
"protocolo": "135260000000099"
}
}
CampoTipoDescripción
event_codestringCódigo del evento en SEFAZ, por ejemplo 110110 carta de corrección, 110180 comprobante de entrega
n_seqstringNúmero de secuencia del evento
protocolostringNúmero de protocolo del evento

Payload de DC-e

Mismo registro y mismas cabeceras que la NF-e; vea DC-e para el flujo de emisión. El objeto de resultado usa tipo = DC-e con 14 campos en orden fijo.

EventoDisparodceStatus
dce.authorizedAutorización 100Autorizada
dce.rejectedRechazo de SEFAZ o fallo terminal de la tarea (incluidos los fallos antes de numerar el documento, que no tienen chave)Negada (dceMotivoStatus es cStat - motivo; un fallo terminal sin cStat trae solo el motivo)
dce.canceledCancelación registrada (135 / 136 / 155)Cancelada (dceDataAutorizacao es el momento de registro de la cancelación, dceNumeroProtocolo el protocolo del evento de cancelación)
dce.cancel_rejectedSEFAZ rechazó la cancelación o la tarea de cancelación falló de forma terminalCancelamentoNegado (el documento sigue autorizado; trae el protocolo de autorización / digest / enlace del XML)

Autorizada:

json
{
"tipo": "DC-e",
"empresaId": "1934811222334455",
"dceId": "DCe-000012333",
"dceStatus": "Autorizada",
"dceMotivoStatus": null,
"dceLinkDace": "https://api.v2.tffiscal.com/openapi/files/dace/35260940673061000134990010000000011100000016?token=...",
"dceLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/7?token=...",
"dceNumero": "1",
"dceSerie": "1",
"dceChaveAcesso": "41260940673061000134990010000000011101234565",
"dceDataEmissao": "2026-09-06T12:00:00Z",
"dceDataAutorizacao": "2026-09-06T12:00:03Z",
"dceNumeroProtocolo": "141260000000001",
"dceDigestValue": "tasG40Ic6Zh7PIBiLEEH7Hb940Y="
}

Rechazada:

json
{
"tipo": "DC-e",
"empresaId": "1934811222334455",
"dceId": "DCe-000012333",
"dceStatus": "Negada",
"dceMotivoStatus": "225 - Rejeicao: Falha no schema XML",
"dceLinkDace": null,
"dceLinkXml": null,
"dceNumero": "1",
"dceSerie": "1",
"dceChaveAcesso": "4126...",
"dceDataEmissao": "2026-09-06T12:00:00Z",
"dceDataAutorizacao": null,
"dceNumeroProtocolo": null,
"dceDigestValue": null
}

Fallo antes de la numeración (empresa no configurada para DC-e, serie ausente, fallo de mapeo del mensaje y otros fallos terminales en los que el documento nunca fue numerado y no tiene chave): dce.rejected se entrega igualmente, los campos de hecho del documento son null, dceMotivoStatus trae el motivo del fallo y dceDataEmissao el momento de aceptación. Correlacione por dceId y nunca suponga que dceChaveAcesso está presente:

json
{
"tipo": "DC-e",
"empresaId": "1934811222334455",
"dceId": "DCe-000012333",
"dceStatus": "Negada",
"dceMotivoStatus": "Empresa não configurada para emissão de DC-e",
"dceLinkDace": null,
"dceLinkXml": null,
"dceNumero": null,
"dceSerie": null,
"dceChaveAcesso": null,
"dceDataEmissao": "2026-09-06T12:00:00Z",
"dceDataAutorizacao": null,
"dceNumeroProtocolo": null,
"dceDigestValue": null
}

Cancelada:

json
{
"tipo": "DC-e",
"empresaId": "1934811222334455",
"dceId": "DCe-000012333",
"dceStatus": "Cancelada",
"dceMotivoStatus": null,
"dceLinkDace": "https://api.v2.tffiscal.com/openapi/files/dace/35260940673061000134990010000000011100000016?token=...",
"dceLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/7?token=...",
"dceNumero": "1",
"dceSerie": "1",
"dceChaveAcesso": "4126...",
"dceDataEmissao": "2026-09-06T12:00:00Z",
"dceDataAutorizacao": "2026-09-06T15:00:00Z",
"dceNumeroProtocolo": "141260000000099",
"dceDigestValue": null
}

Cancelación rechazada:

json
{
"tipo": "DC-e",
"empresaId": "1934811222334455",
"dceId": "DCe-000012333",
"dceStatus": "CancelamentoNegado",
"dceMotivoStatus": "594 - Rejeicao: O numero de sequencia do evento informado e maior que o permitido",
"dceLinkDace": null,
"dceLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/7?token=...",
"dceNumero": "1",
"dceSerie": "1",
"dceChaveAcesso": "4126...",
"dceDataEmissao": "2026-09-06T12:00:00Z",
"dceDataAutorizacao": null,
"dceNumeroProtocolo": "141260000000001",
"dceDigestValue": "tasG40Ic6Zh7PIBiLEEH7Hb940Y="
}
CampoTipoDescripción
tipostringSiempre DC-e
empresaIdstringIdentificador de la empresa
dceIdstringEl id enviado en la emisión; clave de correlación de todos los eventos, presente incluso cuando el documento nunca fue numerado
dceStatusstringAutorizada / Negada / Cancelada / CancelamentoNegado
dceMotivoStatusstring | nullcStat - motivo en un rechazo de SEFAZ; solo el motivo en un fallo terminal sin cStat; null en éxito
dceLinkDacestring | nullEnlace de descarga del PDF del DACE; null cuando está rechazada y en CancelamentoNegado
dceLinkXmlstring | nullEnlace de descarga del XML autorizado; null cuando está rechazada
dceNumerostring | nullNúmero del documento; null cuando nunca fue numerado
dceSeriestring | nullSerie; null cuando nunca fue numerado
dceChaveAcessostring | nullClave de acceso de 44 dígitos; null cuando nunca fue numerado
dceDataEmissaostringFecha de emisión, ISO-8601 UTC (momento de aceptación en un fallo antes de la numeración)
dceDataAutorizacaostring | nullFecha de autorización; momento de registro de la cancelación en Cancelada; null cuando está rechazada
dceNumeroProtocolostring | nullProtocolo de autorización; protocolo del evento de cancelación en Cancelada; null cuando está rechazada
dceDigestValuestring | nullDigest de la firma del XML autorizado; null cuando está rechazada y en Cancelada

Ambos enlaces comparten la forma de la API de consulta (/openapi/files/{kind}/{ref}?token=...) y su vigencia viene de la configuración del tenant (7 días por defecto), vea Descarga de archivos.

Payload de NFC-e

Reutiliza la URL de callback y las cabeceras de la NF-e; el flujo de emisión está en NFC-e. Estos cinco eventos no forman parte de la suscripción por defecto: lístelos en eventos al registrar el webhook para que se entreguen. El objeto de resultado tiene los campos de la NF-e con tipo = NFC-e y el contenido del QR Code en nfeQrCode; los 14 campos tienen orden fijo y aparecen aunque estén vacíos.

Código del eventoDisparonfeStatus
nfce.authorizedAutorizada, o autorizada tras la transmisión de una nota offlineAutorizada
nfce.rejectedRechazo de la SEFAZ, fallo en el procesamiento de la plataforma, o rechazo de una nota offline transmitidaNegada (nfeMotivoStatus trae código y motivo)
nfce.canceledCancelación o cancelación por sustitución registradaCancelada (nfeDataAutorizacao es el momento de la cancelación, nfeNumeroProtocolo el protocolo de la cancelación)
nfce.cancel_rejectedSEFAZ rechazó la cancelaciónCancelamentoNegado (la nota sigue válida; motivo en nfeMotivoStatus)
nfce.contingency_issuedEmitida offline con la SEFAZ caídaContingenciaPendente (enlace del cupón y nfeQrCode, sin protocolo)

Autorizada:

json
{
"tipo": "NFC-e",
"empresaId": "1934811222334455",
"nfeId": "PDV01-000123",
"nfeStatus": "Autorizada",
"nfeMotivoStatus": null,
"nfeLinkDanfe": "https://api.v2.tffiscal.com/openapi/files/danfce/35261111222333000181650010000001231123456780?token=MXw3fDEwMXwxNzU4...",
"nfeLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/9901?token=MXw3fDEwMXwxNzU4...",
"nfeNumero": "123",
"nfeSerie": "1",
"nfeChaveAcesso": "35261111222333000181650010000001231123456780",
"nfeDataEmissao": "2026-11-04T15:20:01Z",
"nfeDataAutorizacao": "2026-11-04T15:20:02Z",
"nfeNumeroProtocolo": "135261234567890",
"nfeQrCode": "https://www.homologacao.nfce.fazenda.sp.gov.br/qrcode?p=35261111222333000181650010000001231123456780|3|2"
}

Rechazada:

json
{
"tipo": "NFC-e",
"empresaId": "1934811222334455",
"nfeId": "PDV01-000123",
"nfeStatus": "Negada",
"nfeMotivoStatus": "725 - Rejeicao: NFC-e com CFOP invalido",
"nfeLinkDanfe": null,
"nfeLinkXml": null,
"nfeNumero": "123",
"nfeSerie": "1",
"nfeChaveAcesso": "35261111222333000181650010000001231123456780",
"nfeDataEmissao": "2026-11-04T15:20:01Z",
"nfeDataAutorizacao": null,
"nfeNumeroProtocolo": null,
"nfeQrCode": null
}
CampoTipoDescripción
tipostringSiempre NFC-e
empresaIdstringIdentificador de la empresa
nfeIdstringEl id enviado en la emisión; relacione los callbacks por él
nfeStatusstringAutorizada / Negada / Cancelada / CancelamentoNegado / ContingenciaPendente
nfeMotivoStatusstring | nullMotivo del rechazo o de la cancelación rechazada; en los demás casos null
nfeLinkDanfestring | nullEnlace del PDF del cupón de 80 mm; null en el rechazo
nfeLinkXmlstring | nullEnlace del XML; null en el rechazo
nfeNumerostring | nullNúmero de la nota
nfeSeriestring | nullSerie
nfeChaveAcessostring | nullClave de acceso de 44 dígitos
nfeDataEmissaostring | nullFecha de emisión, ISO-8601 UTC
nfeDataAutorizacaostring | nullFecha de autorización; en Cancelada, el momento de la cancelación
nfeNumeroProtocolostring | nullProtocolo de autorización; en Cancelada, el protocolo de la cancelación
nfeQrCodestring | nullContenido del QR Code; codifique la cadena entera al imprimir el cupón

Payload de NFS-e

Mismo registro y mismas cabeceras que NF-e; vea NFS-e para el flujo de emisión. El objeto de resultado usa tipo = NFS-e, con orden fijo de campos.

EventoDisparonfseStatus
nfse.authorizedEmitida con éxitoAutorizada
nfse.rejectedRechazada por la autoridad o fallo de nuestro ladoNegada
nfse.canceledCancelación completada (incluido análisis aprobado)Cancelada
nfse.cancel_requestedAnálisis de cancelación aceptadoEmProcessoDeCancelamento
nfse.cancel_deniedLa cancelación no ocurrió y el documento sigue autorizado: rechazo síncrono de la autoridad, envío agotado, o solicitud de análisis denegada por el municipioAutorizada
nfse.substitutedEste documento fue sustituido; la carga trae un nfseChaveSubstituta adicionalSubstituida
nfse.inbound.receivedNFS-e recibida (este emisor es receptor / intermediario, requiere suscripción explícita)—
nfse.inbound.canceledNFS-e recibida cancelada (requiere suscripción explícita)—
nfse.inbound.substitutedNFS-e recibida sustituida (requiere suscripción explícita)—

Aviso: Semántica de las suscripciones. Una aplicación que nunca registró ninguna fila de suscripción recibe los seis eventos de salida. En cuanto exista cualquier fila de suscripción para la aplicación (por ejemplo un evento de entrada o de e-mail de facturas), pasa al modo lista de permitidos y solo se entregan los eventos habilitados explícitamente. Al suscribirse a eventos de entrada, habilite también los eventos de salida que necesite, o las notificaciones de salida quedarán en espera de destinatario.

Autorizada:

json
{
"tipo": "NFS-e",
"empresaId": "1001",
"nfseId": "PED-20260918-001",
"nfseStatus": "Autorizada",
"nfseMotivoStatus": null,
"nfseLinkDanfse": "https://api.v2.tffiscal.com/openapi/files/danfse/…",
"nfseLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/9001?token=…",
"nfseNumero": "1287",
"nfseSerie": "1",
"nfseChaveAcesso": "3550308…",
"nfseIdentificacaoDps": "DPS3550308…",
"nfseDataEmissao": "2026-09-18T13:00:00Z",
"nfseDataAutorizacao": "2026-09-18T13:00:03Z",
"nfseNumeroProtocolo": null,
"nfseAtualizadoEm": "2026-09-18T13:00:03Z"
}
CampoTipoDescripción
tipostringSiempre NFS-e
empresaIdstringIdentificador del emisor
nfseIdstringEl id enviado en la emisión; clave de correlación de todos los eventos, presente incluso cuando la emisión falla
nfseStatusstringAutorizada / Negada / Cancelada / EmProcessoDeCancelamento / Substituida
nfseMotivoStatusstring | nullEn el fallo, el código y la descripción de la autoridad, o el motivo del rechazo de la plataforma; en nfse.cancel_denied, el código y el texto del veredicto de la cancelación; null en el éxito
nfseLinkDanfsestring | nullEnlace de descarga del PDF del DANFSe; null cuando no hay
nfseLinkXmlstring | nullEnlace de descarga del XML original oficial; null cuando no hay
nfseNumerostring | nullNúmero de la NFS-e asignado por la autoridad; null cuando la emisión no tuvo éxito
nfseSeriestring | nullSerie; null cuando la emisión no tuvo éxito
nfseChaveAcessostring | nullClave de acceso de 50 dígitos, generada por la autoridad; null cuando la emisión falla
nfseIdentificacaoDpsstring | nullIdentificador del DPS de 45 caracteres; única clave de conciliación utilizable en un documento fallido, y también null en fallos anteriores a la numeración
nfseDataEmissaostringFecha de emisión, ISO-8601 UTC
nfseDataAutorizacaostring | nullFecha de autorización; null cuando no está autorizada
nfseNumeroProtocolostring | nullProtocolo de la autoridad; null cuando la autoridad no devuelve ninguno
nfseAtualizadoEmstringMomento de este cambio de estado, ISO-8601 UTC
nfseChaveSubstitutastringClave de acceso de 50 dígitos del documento sustituto, presente solo en nfse.substituted

nfseAtualizadoEm es el momento de este cambio de estado. El orden de entrega no está garantizado: los reintentos y las múltiples instancias pueden hacer que un evento antiguo llegue después de uno nuevo. Compárelo con la marca de tiempo que ya tiene y descarte el más antiguo en vez de sobrescribir el estado por orden de llegada.

En un callback de fallo el nfseChaveAcesso puede venir vacío (la autoridad nunca generó la clave); concilie por nfseIdentificacaoDps en ese caso. Un callback de fallo anterior a la numeración no trae ninguno de los dos, y solo el nfseId concilia.

La ventana de cancelación y el flujo de análisis están en Cancelar NFS-e; las facturas recibidas y la manifestación, en NFS-e recibidas.

Payload del veredicto de verificación

Cuando la verificación de nivel 2 en SEFAZ se resuelve, la plataforma envía invoice.verify.completed a su URL de webhook. Este es el único canal push para veredictos finales; la consulta por chave puede servir como respaldo por polling.

json
{
"version": "1.0",
"event_id": "1950000000000001",
"event_type": "invoice.verify.completed",
"occurred_at": "2026-07-23T17:16:23Z",
"data": {
"chaveAcesso": "35260764962869000108550990001366171195929648",
"validationStatus": "VALIDATED",
"status": "Autorizada",
"cStat": "100",
"xMotivo": "Autorizado o uso da NF-e",
"protocolo": { "numero": "135262955451772", "digestValue": "oAEE...HwY=" },
"dataAutorizacao": "2026-07-23T14:30:09Z",
"eventos": [],
"verifiedAt": "2026-07-23T17:16:23Z",
"reason": "present only for REJECTED / VALIDATION_ERROR (stable English text)"
}
}

Sobre:

CampoTipoPresenciaDescripción
versionstringsiempreVersión del esquema del payload, actualmente 1.0
event_idstringsiempreId del evento, clave de idempotencia, idéntico entre reintentos
event_typestringsiempreinvoice.verify.completed
occurred_atstringsiempreMomento del evento, ISO-8601 UTC
dataobjectsiempreCuerpo del veredicto, abajo

data:

CampoTipoPresenciaDescripción
chaveAcessostringsiempreClave de acceso de 44 dígitos de la factura verificada, la clave de unión con su envío
validationStatusstringsiempreVeredicto final: VALIDATED / REJECTED / VALIDATION_ERROR, vea Verificación de NF-e
statusstring | nullanulableEstado fiscal en SEFAZ: Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida; null en VALIDATION_ERROR (no se obtuvo veredicto de SEFAZ)
cStatstring | nullanulableCódigo de retorno crudo de SEFAZ (por ejemplo 100 autorizada, 101 cancelada); null cuando no se alcanzó SEFAZ
xMotivostring | nullanulableMensaje de retorno crudo de SEFAZ (portugués, sin cambios)
protocoloobject | nullanulableObjeto de protocolo (numero, digestValue) del registro oficial
dataAutorizacaostring | nullanulableFecha de autorización en SEFAZ, ISO-8601 UTC
eventos[]arraysiempre (puede estar vacío)Eventos fiscales registrados sobre la factura (cancelación, cartas de corrección); vacío cuando no hay
verifiedAtstringsiempreCuándo se completó la verificación de nivel 2, ISO-8601 UTC
reasonstringsolo en falloPresente solo en REJECTED / VALIDATION_ERROR; texto estable en inglés que explica el veredicto
validationStatusTerminalAcción
VALIDATEDSíSeguro continuar (liberar mercancía, liquidar)
REJECTEDSíNo continúe; reason explica el veredicto de SEFAZ (cancelada / denegada / inutilizada / no encontrada / protocolo divergente)
VALIDATION_ERRORSí, reintentableFallo de verificación del lado de la plataforma, no es un juicio sobre la factura; como cualquier veredicto, se reutiliza durante 24 horas, así que reenvíe más tarde por la verificación de XML con la cabecera forceRevalidate: true

Los payloads de webhook solo traen valores de enumeración independientes del idioma; no hay campos *Description. El texto de presentación queda a cargo del receptor.

Payload del e-mail de facturas

Cuando un correo aceptado en Enviar correo de la factura llega a su estado terminal, la plataforma envía email.sent (el servicio de envío aceptó el mensaje) o email.failed (fallo definitivo). Ambos requieren suscripción explícita: solo se entregan si están en eventos en el registro del webhook. Tras cualquiera de ellos el mismo documento acepta un nuevo envío. El payload es el sobre de evento, con los campos de data en snake_case.

json
{
"version": "1.0",
"event_id": "1950000000000006",
"event_type": "email.sent",
"occurred_at": "2026-09-28T12:00:31Z",
"data": {
"email_id": "2103160000000000001",
"id": "email-pedido-8842-1",
"tipo_documento": "NFe",
"documento_id": "pedido-8842",
"chave": "35241204893402000113650010000117691017244265",
"destinatarios": ["comprador@exemplo.com"],
"status": "Enviado",
"motivo_falha": null
}
}

data:

CampoTipoPresenciaDescripción
email_idstringsiempreEl emailId devuelto en la aceptación; úselo en Consultar correo de la factura
idstringsiempreIdentificador de la solicitud enviado en el envío
tipo_documentostringsiempreNFe / NFSe / CTe / DCe
documento_idstringsiempreEl id enviado en la emisión
chavestring | nullopcionalClave de acceso, 44 o 50 dígitos; null cuando el fallo ocurrió antes de que existiera la clave
destinatariosarraysiempreDestinatarios efectivos
statusstringsiempreEnviado (email.sent) o Falha (email.failed)
motivo_falhastring | nullopcionalMotivo del fallo, texto libre; null en email.sent

Ejemplo de fallo (NFS-e de São Paulo cuya espera por la clave se agotó):

json
{
"version": "1.0",
"event_id": "1950000000000007",
"event_type": "email.failed",
"occurred_at": "2026-09-28T14:00:30Z",
"data": {
"email_id": "2103160000000000002",
"id": "mail-sp-000045",
"tipo_documento": "NFSe",
"documento_id": "SP-20260928-045",
"chave": null,
"destinatarios": ["financeiro@cliente.com.br"],
"status": "Falha",
"motivo_falha": "等待超时:chaveAcesso"
}
}

email.sent significa que el servicio de envío aceptó el mensaje, no que llegó a la bandeja del destinatario. Plantillas, adjuntos y estados están en E-mail de facturas.

Reintentos y circuit breaker

  • Cualquier estado 2xx confirma la entrega. Cualquier otro estado, un error de red o la falta de respuesta en 10 segundos cuenta como fallo.
  • Las entregas fallidas se reintentan con backoff tras el intento inicial:
text
1 min, 5 min, 30 min, 2 h, 6 h
  • Cinco reintentos (seis intentos en total, en unas 8,6 horas). Después, la plataforma puede reenviar la entrega manualmente a petición.
  • Circuit breaker: los fallos de entrega consecutivos (10 por defecto) desactivan automáticamente el webhook. Mientras está desactivado no se encola ningún evento nuevo para él. Llamar de nuevo a Registrar webhook (o revalidar y guardar la URL en la consola) restaura la entrega y pone a cero el contador de fallos; pida a la plataforma el reenvío de los eventos generados mientras el webhook estaba desactivado.
  • Un nuevo registro surte efecto en el siguiente reintento.

Requisitos del receptor

  1. Responda 2xx en menos de 10 segundos. Buena práctica: persista la entrega cruda, responda 2xx de inmediato y procese de forma asíncrona.
  2. Verifique el origen: recalcule el HMAC sobre los bytes crudos recibidos; si definió un token de callback, compare también la cabecera token con él.
  3. Deduplique por el id del evento (X-Tffiscal-Event-Id o event_id en los payloads con sobre).
  4. URL pública: una URL absoluta http / https accesible desde internet, de como máximo 512 caracteres. Las direcciones de loopback, privadas y link-local se rechazan en el registro.
  5. Responda a la entrega de prueba: guardar la URL en la consola envía un evento con X-Tffiscal-Event: webhook.verify (con token / x-token cuando hay token definido); responda 2xx sin ningún procesamiento de negocio.
  6. Condicione las acciones de negocio a los campos de resultado (nfeStatus, cteStatus, dceStatus, nfseStatus, validationStatus), nunca solo a la respuesta síncrona de la API.
  7. Nunca suponga que los campos opcionales están presentes: dceChaveAcesso puede ser null, reason solo aparece en fallo y los campos vacíos de NF-e llegan como valores vacíos.

Solución de problemas

¿No llegan los callbacks?

Confirme que la URL es una dirección https/http accesible públicamente y que responde 2xx en menos de 10 segundos. La plataforma reintenta con backoff y desactiva el webhook tras fallos consecutivos; llamar de nuevo al endpoint de registro restaura la entrega.

¿La firma falla siempre?

La causa más común es deserializar el payload y volver a serializarlo antes de calcular el HMAC, lo que cambia el orden de los campos o los espacios. Calcule siempre el hash sobre los bytes crudos recibidos y concatene el valor de X-Tffiscal-Timestamp exactamente como lo recibió.

¿El mismo evento llegó dos veces?

Es lo esperado con la entrega al menos una vez. Deduplique por el id del evento; el id de entrega varía por destino y no debe usarse para eso.