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 coneventosal 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
tokende callback es opcional. Cuando está definido (en la consola o en el registro), cada callback lo envía sin cambios en las cabecerastokenyx-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-Idy, en los payloads con sobre,event_id): es idéntico en todos los intentos del mismo evento. - Cada entrega es un HTTP
POSTcon 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
| Evento | Se dispara cuando | Payload |
|---|---|---|
invoice.authorized | NF-e autorizada por SEFAZ (cStat 100) | Objeto de resultado NF-e, nfeStatus = Autorizada |
invoice.rejected | NF-e rechazada por SEFAZ | Objeto de resultado NF-e, nfeStatus = Negada, motivo en nfeMotivoStatus |
invoice.canceled | Cancelación de la NF-e registrada en SEFAZ (cStat 135) | Sobre de evento, data trae invoice_id / chave / protocolo |
invoice.cce.registered | Carta de corrección (CC-e) registrada en una NF-e | Sobre 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.completed | La verificación de NF-e llegó al veredicto final (VALIDATED / REJECTED / VALIDATION_ERROR) | Sobre de evento con el veredicto en data |
cte.authorized | CT-e autorizado (cStat 100) | Objeto de resultado CT-e, cteStatus = Autorizada |
cte.rejected | CT-e rechazado por SEFAZ (cStat distinto de 100) | Objeto de resultado CT-e, cteStatus = Negada, cteMotivoStatus = cStat - motivo |
cte.canceled | Cancelación del CT-e registrada (cStat 135) | Objeto de resultado CT-e, cteStatus = Cancelada |
cte.event.registered | Cualquier 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.authorized | DC-e autorizada (cStat 100) | Objeto de resultado DC-e, dceStatus = Autorizada |
dce.rejected | DC-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.canceled | Cancelación de la DC-e registrada (cStat 135 / 136 / 155) | Objeto de resultado DC-e, dceStatus = Cancelada |
dce.cancel_rejected | SEFAZ rechazó la cancelación de la DC-e, o la tarea de cancelación falló de forma terminal | Objeto de resultado DC-e, dceStatus = CancelamentoNegado, el documento sigue autorizado |
nfce.authorized | NFC-e autorizada; también se envía cuando una nota offline se autoriza tras la transmisión | Objeto de resultado NFC-e, nfeStatus = Autorizada |
nfce.rejected | NFC-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ón | Objeto de resultado NFC-e, nfeStatus = Negada |
nfce.canceled | Cancelación de la NFC-e (incluida la cancelación por sustitución) registrada | Objeto de resultado NFC-e, nfeStatus = Cancelada |
nfce.cancel_rejected | SEFAZ rechazó la cancelación de la NFC-e | Objeto de resultado NFC-e, nfeStatus = CancelamentoNegado, la nota sigue válida |
nfce.contingency_issued | NFC-e emitida offline con la SEFAZ caída | Objeto de resultado NFC-e, nfeStatus = ContingenciaPendente |
nfse.authorized | NFS-e emitida con éxito | Objeto de resultado NFS-e, nfseStatus = Autorizada |
nfse.rejected | NFS-e rechazada por la autoridad, o fallo del lado de la plataforma | Objeto de resultado NFS-e, nfseStatus = Negada, motivo en nfseMotivoStatus |
nfse.canceled | Cancelación de la NFS-e completada (incluido análisis aprobado) | Objeto de resultado NFS-e, nfseStatus = Cancelada |
nfse.cancel_requested | Análisis de cancelación de la NFS-e aceptado | Objeto de resultado NFS-e, nfseStatus = EmProcessoDeCancelamento |
nfse.cancel_denied | La cancelación de la NFS-e no ocurrió y el documento sigue autorizado | Objeto de resultado NFS-e, nfseStatus = Autorizada, motivo en nfseMotivoStatus |
nfse.substituted | NFS-e sustituida por otro documento | Objeto de resultado NFS-e, nfseStatus = Substituida, más nfseChaveSubstituta |
nfse.inbound.received | NFS-e recibida, siendo este emisor receptor / intermediario (requiere suscripción explícita) | Notificación de NFS-e recibida |
nfse.inbound.canceled | NFS-e recibida cancelada (requiere suscripción explícita) | Notificación de NFS-e recibida |
nfse.inbound.substituted | NFS-e recibida sustituida (requiere suscripción explícita) | Notificación de NFS-e recibida |
nfse.inbound.manifested | Se 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_failed | Falló 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.sent | E-mail de la factura entregado al servicio de envío (requiere suscripción explícita) | Sobre de evento, data.status = Enviado |
email.failed | E-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 campoevent_id; deduplique por la cabeceraX-Tffiscal-Event-Id. - Sobre de evento:
{ "version", "event_id", "event_type", "occurred_at", "data" }. Lo usaninvoice.canceled,invoice.cce.registered,invoice.verify.completed,cte.event.registered,nfse.inbound.*yemail.sent/email.failed. Los campos dentro dedatasolo se añaden, nunca se renombran ni se eliminan; un cambio incompatible incrementaversion.
Nota: los rechazos de SEFAZ nunca son errores HTTP de la llamada de emisión. Aparecen como estado
Negadaen la consulta y como eventoinvoice.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
| Cabecera | Significado |
|---|---|
X-Tffiscal-Event | Có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-Id | Id del evento, la clave de idempotencia; no cambia entre reintentos y es compartido por todos los destinos del mismo evento |
X-Tffiscal-Delivery-Id | Identificador de la entrega; no deduplique por él, use el id del evento |
X-Tffiscal-Timestamp | Segundos Unix, regenerado en cada intento |
X-Tffiscal-Signature | hex( HMAC-SHA256( app_secret, timestamp + "." + body ) ), en minúsculas |
token | Token de callback opcional: el valor definido en la consola o enviado en Registrar webhook, devuelto sin cambios. Ausente cuando no hay token definido |
x-token | Mismo 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:
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:
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:
{"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:
{"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"}
| Campo | Tipo | Descripción |
|---|---|---|
tipo | string | Siempre NF-e |
empresaId | string | Identificador de la empresa |
nfeId | string | El id enviado en la emisión |
nfeStatus | string | Autorizada / Negada |
nfeMotivoStatus | string | Motivo de la denegación: código de estado de SEFAZ + descripción; vacío cuando está autorizada |
nfeLinkDanfe | string | Enlace de descarga del PDF del DANFE (vea Descarga de archivos; utilizable en el momento del callback); vacío cuando está denegada |
nfeLinkXml | string | Enlace de descarga del XML autorizado; vacío cuando está denegada |
nfeNumero | string | Número de la factura |
nfeSerie | string | Serie |
nfeChaveAcesso | string | Clave de acceso de 44 dígitos |
nfeDataEmissao | string | Fecha de emisión, ISO-8601 UTC |
nfeDataAutorizacao | string | Fecha de autorización; vacío cuando está denegada |
nfeNumeroProtocolo | string | Número de protocolo de autorización; vacío cuando está denegada |
nfeDigestValue | string | Digest 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.
| Evento | Disparo | cteStatus |
|---|---|---|
cte.authorized | Autorización 100 | Autorizada |
cte.rejected | Rechazo 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.canceled | Cancelación 135 | Cancelada |
cte.event.registered | Cualquier otro evento registrado | Sobre de evento, data trae event_code / n_seq / protocolo |
Autorizado:
{"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": "..."}
| Campo | Tipo | Descripción |
|---|---|---|
tipo | string | Siempre CT-e |
empresaId | string | Identificador de la empresa |
cteId | string | El id enviado en la emisión |
cteStatus | string | Autorizada / Negada / Cancelada |
cteMotivoStatus | string | null | cStat - motivo cuando está rechazado; null en los demás casos |
cteLinkDacte | string | null | Enlace de descarga del PDF del DACTE; null cuando está rechazado |
cteLinkXml | string | null | Enlace de descarga del XML autorizado; null cuando está rechazado |
cteNumero | string | Número del documento |
cteSerie | string | Serie |
cteChaveAcesso | string | Clave de acceso de 44 dígitos |
cteDataEmissao | string | Fecha de emisión, ISO-8601 UTC |
cteDataAutorizacao | string | null | Fecha de autorización; null cuando está rechazado |
cteNumeroProtocolo | string | null | Número de protocolo de autorización; null cuando está rechazado |
cteDigestValue | string | null | Digest 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):
{"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"}}
| Campo | Tipo | Descripción |
|---|---|---|
event_code | string | Código del evento en SEFAZ, por ejemplo 110110 carta de corrección, 110180 comprobante de entrega |
n_seq | string | Número de secuencia del evento |
protocolo | string | Nú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.
| Evento | Disparo | dceStatus |
|---|---|---|
dce.authorized | Autorización 100 | Autorizada |
dce.rejected | Rechazo 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.canceled | Cancelació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_rejected | SEFAZ rechazó la cancelación o la tarea de cancelación falló de forma terminal | CancelamentoNegado (el documento sigue autorizado; trae el protocolo de autorización / digest / enlace del XML) |
Autorizada:
{"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:
{"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:
{"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:
{"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:
{"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="}
| Campo | Tipo | Descripción |
|---|---|---|
tipo | string | Siempre DC-e |
empresaId | string | Identificador de la empresa |
dceId | string | El id enviado en la emisión; clave de correlación de todos los eventos, presente incluso cuando el documento nunca fue numerado |
dceStatus | string | Autorizada / Negada / Cancelada / CancelamentoNegado |
dceMotivoStatus | string | null | cStat - motivo en un rechazo de SEFAZ; solo el motivo en un fallo terminal sin cStat; null en éxito |
dceLinkDace | string | null | Enlace de descarga del PDF del DACE; null cuando está rechazada y en CancelamentoNegado |
dceLinkXml | string | null | Enlace de descarga del XML autorizado; null cuando está rechazada |
dceNumero | string | null | Número del documento; null cuando nunca fue numerado |
dceSerie | string | null | Serie; null cuando nunca fue numerado |
dceChaveAcesso | string | null | Clave de acceso de 44 dígitos; null cuando nunca fue numerado |
dceDataEmissao | string | Fecha de emisión, ISO-8601 UTC (momento de aceptación en un fallo antes de la numeración) |
dceDataAutorizacao | string | null | Fecha de autorización; momento de registro de la cancelación en Cancelada; null cuando está rechazada |
dceNumeroProtocolo | string | null | Protocolo de autorización; protocolo del evento de cancelación en Cancelada; null cuando está rechazada |
dceDigestValue | string | null | Digest 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 evento | Disparo | nfeStatus |
|---|---|---|
nfce.authorized | Autorizada, o autorizada tras la transmisión de una nota offline | Autorizada |
nfce.rejected | Rechazo de la SEFAZ, fallo en el procesamiento de la plataforma, o rechazo de una nota offline transmitida | Negada (nfeMotivoStatus trae código y motivo) |
nfce.canceled | Cancelación o cancelación por sustitución registrada | Cancelada (nfeDataAutorizacao es el momento de la cancelación, nfeNumeroProtocolo el protocolo de la cancelación) |
nfce.cancel_rejected | SEFAZ rechazó la cancelación | CancelamentoNegado (la nota sigue válida; motivo en nfeMotivoStatus) |
nfce.contingency_issued | Emitida offline con la SEFAZ caída | ContingenciaPendente (enlace del cupón y nfeQrCode, sin protocolo) |
Autorizada:
{"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:
{"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}
| Campo | Tipo | Descripción |
|---|---|---|
tipo | string | Siempre NFC-e |
empresaId | string | Identificador de la empresa |
nfeId | string | El id enviado en la emisión; relacione los callbacks por él |
nfeStatus | string | Autorizada / Negada / Cancelada / CancelamentoNegado / ContingenciaPendente |
nfeMotivoStatus | string | null | Motivo del rechazo o de la cancelación rechazada; en los demás casos null |
nfeLinkDanfe | string | null | Enlace del PDF del cupón de 80 mm; null en el rechazo |
nfeLinkXml | string | null | Enlace del XML; null en el rechazo |
nfeNumero | string | null | Número de la nota |
nfeSerie | string | null | Serie |
nfeChaveAcesso | string | null | Clave de acceso de 44 dígitos |
nfeDataEmissao | string | null | Fecha de emisión, ISO-8601 UTC |
nfeDataAutorizacao | string | null | Fecha de autorización; en Cancelada, el momento de la cancelación |
nfeNumeroProtocolo | string | null | Protocolo de autorización; en Cancelada, el protocolo de la cancelación |
nfeQrCode | string | null | Contenido 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.
| Evento | Disparo | nfseStatus |
|---|---|---|
nfse.authorized | Emitida con éxito | Autorizada |
nfse.rejected | Rechazada por la autoridad o fallo de nuestro lado | Negada |
nfse.canceled | Cancelación completada (incluido análisis aprobado) | Cancelada |
nfse.cancel_requested | Análisis de cancelación aceptado | EmProcessoDeCancelamento |
nfse.cancel_denied | La 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 municipio | Autorizada |
nfse.substituted | Este documento fue sustituido; la carga trae un nfseChaveSubstituta adicional | Substituida |
nfse.inbound.received | NFS-e recibida (este emisor es receptor / intermediario, requiere suscripción explícita) | — |
nfse.inbound.canceled | NFS-e recibida cancelada (requiere suscripción explícita) | — |
nfse.inbound.substituted | NFS-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:
{"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"}
| Campo | Tipo | Descripción |
|---|---|---|
tipo | string | Siempre NFS-e |
empresaId | string | Identificador del emisor |
nfseId | string | El id enviado en la emisión; clave de correlación de todos los eventos, presente incluso cuando la emisión falla |
nfseStatus | string | Autorizada / Negada / Cancelada / EmProcessoDeCancelamento / Substituida |
nfseMotivoStatus | string | null | En 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 |
nfseLinkDanfse | string | null | Enlace de descarga del PDF del DANFSe; null cuando no hay |
nfseLinkXml | string | null | Enlace de descarga del XML original oficial; null cuando no hay |
nfseNumero | string | null | Número de la NFS-e asignado por la autoridad; null cuando la emisión no tuvo éxito |
nfseSerie | string | null | Serie; null cuando la emisión no tuvo éxito |
nfseChaveAcesso | string | null | Clave de acceso de 50 dígitos, generada por la autoridad; null cuando la emisión falla |
nfseIdentificacaoDps | string | null | Identificador 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 |
nfseDataEmissao | string | Fecha de emisión, ISO-8601 UTC |
nfseDataAutorizacao | string | null | Fecha de autorización; null cuando no está autorizada |
nfseNumeroProtocolo | string | null | Protocolo de la autoridad; null cuando la autoridad no devuelve ninguno |
nfseAtualizadoEm | string | Momento de este cambio de estado, ISO-8601 UTC |
nfseChaveSubstituta | string | Clave 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.
{"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:
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
version | string | siempre | Versión del esquema del payload, actualmente 1.0 |
event_id | string | siempre | Id del evento, clave de idempotencia, idéntico entre reintentos |
event_type | string | siempre | invoice.verify.completed |
occurred_at | string | siempre | Momento del evento, ISO-8601 UTC |
data | object | siempre | Cuerpo del veredicto, abajo |
data:
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
chaveAcesso | string | siempre | Clave de acceso de 44 dígitos de la factura verificada, la clave de unión con su envío |
validationStatus | string | siempre | Veredicto final: VALIDATED / REJECTED / VALIDATION_ERROR, vea Verificación de NF-e |
status | string | null | anulable | Estado fiscal en SEFAZ: Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida; null en VALIDATION_ERROR (no se obtuvo veredicto de SEFAZ) |
cStat | string | null | anulable | Código de retorno crudo de SEFAZ (por ejemplo 100 autorizada, 101 cancelada); null cuando no se alcanzó SEFAZ |
xMotivo | string | null | anulable | Mensaje de retorno crudo de SEFAZ (portugués, sin cambios) |
protocolo | object | null | anulable | Objeto de protocolo (numero, digestValue) del registro oficial |
dataAutorizacao | string | null | anulable | Fecha de autorización en SEFAZ, ISO-8601 UTC |
eventos[] | array | siempre (puede estar vacío) | Eventos fiscales registrados sobre la factura (cancelación, cartas de corrección); vacío cuando no hay |
verifiedAt | string | siempre | Cuándo se completó la verificación de nivel 2, ISO-8601 UTC |
reason | string | solo en fallo | Presente solo en REJECTED / VALIDATION_ERROR; texto estable en inglés que explica el veredicto |
validationStatus | Terminal | Acción |
|---|---|---|
VALIDATED | Sí | Seguro continuar (liberar mercancía, liquidar) |
REJECTED | Sí | No continúe; reason explica el veredicto de SEFAZ (cancelada / denegada / inutilizada / no encontrada / protocolo divergente) |
VALIDATION_ERROR | Sí, reintentable | Fallo 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.
{"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:
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
email_id | string | siempre | El emailId devuelto en la aceptación; úselo en Consultar correo de la factura |
id | string | siempre | Identificador de la solicitud enviado en el envío |
tipo_documento | string | siempre | NFe / NFSe / CTe / DCe |
documento_id | string | siempre | El id enviado en la emisión |
chave | string | null | opcional | Clave de acceso, 44 o 50 dígitos; null cuando el fallo ocurrió antes de que existiera la clave |
destinatarios | array | siempre | Destinatarios efectivos |
status | string | siempre | Enviado (email.sent) o Falha (email.failed) |
motivo_falha | string | null | opcional | Motivo del fallo, texto libre; null en email.sent |
Ejemplo de fallo (NFS-e de São Paulo cuya espera por la clave se agotó):
{"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:
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
- Responda 2xx en menos de 10 segundos. Buena práctica: persista la entrega cruda, responda 2xx de inmediato y procese de forma asíncrona.
- Verifique el origen: recalcule el HMAC sobre los bytes crudos recibidos; si definió un token de callback, compare también la cabecera
tokencon él. - Deduplique por el id del evento (
X-Tffiscal-Event-Idoevent_iden los payloads con sobre). - URL pública: una URL absoluta
http/httpsaccesible desde internet, de como máximo 512 caracteres. Las direcciones de loopback, privadas y link-local se rechazan en el registro. - Responda a la entrega de prueba: guardar la URL en la consola envía un evento con
X-Tffiscal-Event: webhook.verify(contoken/x-tokencuando hay token definido); responda 2xx sin ningún procesamiento de negocio. - 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. - Nunca suponga que los campos opcionales están presentes:
dceChaveAcessopuede sernull,reasonsolo 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.
