Webhooks
TF Fiscal webhook 的事件目录、投递请求头、HMAC-SHA256 签名、各单据类型载荷、重试与熔断。
投递模型
平台把单据结果与验证结论主动推送到你的服务器,无需轮询。
- 每个应用一个回调地址,可以在控制台(应用详情里的「Webhook 配置」)配置,也可以调用 Webhook 注册 登记。两种方式写入的是同一份配置、效果完全一致,后保存的覆盖先保存的;回调格式与请求头不会因为在哪里配置而不同。控制台目前只提供
invoice.verify.completed的订阅选项,其余事件请在注册时用eventos订阅。NF-e、NFC-e、CT-e、DC-e、NFS-e 的结果以及 NF-e 验证结论都投递到这个地址。 - 回调令牌
token选填。 设置了(在控制台填写,或注册时提交),每次回调都会在请求头token与x-token中原样带回,不分事件类型;没设置就不带这两个头。控制台只显示令牌掩码,可以修改或清除。令牌只能包含可见 ASCII 字符(不含空格),最长 256 位。 - 至少一次投递。 超时后的重试可能在原尝试实际已被处理的情况下再次到达,同一事件也可能扇出到多个目标。请按事件 id(请求头
X-Tffiscal-Event-Id,统一事件体里另有event_id)去重:同一事件的所有尝试共用同一个值。 - 每次投递都是向登记地址发起的 HTTP
POST,JSON 请求体(Content-Type: application/json; charset=utf-8)。 - 测试环境与生产环境的单据遵循同一套契约、签名与重试计划,见环境。
事件目录
| 事件 | 触发时机 | 载荷 |
|---|---|---|
invoice.authorized | NF-e 被 SEFAZ 授权(cStat 100) | NF-e 结果对象,nfeStatus = Autorizada |
invoice.rejected | NF-e 被 SEFAZ 驳回 | NF-e 结果对象,nfeStatus = Negada,原因在 nfeMotivoStatus |
invoice.canceled | NF-e 取消在 SEFAZ 登记完成(cStat 135) | 统一事件体,data 含 invoice_id / chave / protocolo |
invoice.cce.registered | NF-e 更正函(CC-e)登记完成 | 统一事件体,data 含 invoice_id / chave / n_seq。预留:CC-e 登记目前为同步返回,本事件暂不推送 |
invoice.verify.completed | NF-e 验证得出最终结论(VALIDATED / REJECTED / VALIDATION_ERROR) | 统一事件体,结论在 data |
cte.authorized | CT-e 授权通过(cStat 100) | CT-e 结果对象,cteStatus = Autorizada |
cte.rejected | CT-e 被 SEFAZ 驳回(cStat 非 100) | CT-e 结果对象,cteStatus = Negada,cteMotivoStatus 为 cStat - 原因 |
cte.canceled | CT-e 取消登记完成(cStat 135) | CT-e 结果对象,cteStatus = Cancelada |
cte.event.registered | CT-e 其他事件登记成功(更正函、送达凭证、送达失败、服务异议及其撤销) | 统一事件体,data 含 event_code / n_seq / protocolo |
dce.authorized | DC-e 授权通过(cStat 100) | DC-e 结果对象,dceStatus = Autorizada |
dce.rejected | DC-e 被 SEFAZ 驳回,或发行任务终局失败(含单据编号前的失败) | DC-e 结果对象,dceStatus = Negada |
dce.canceled | DC-e 取消登记成功(cStat 135 / 136 / 155) | DC-e 结果对象,dceStatus = Cancelada |
dce.cancel_rejected | DC-e 取消被 SEFAZ 拒绝,或取消任务终局失败 | DC-e 结果对象,dceStatus = CancelamentoNegado,单据仍为已授权 |
nfce.authorized | NFC-e 授权通过;离线票补传授权后也推送 | NFC-e 结果对象,nfeStatus = Autorizada |
nfce.rejected | NFC-e 被 SEFAZ 驳回,或平台后台处理失败;离线票补传被拒也推送 | NFC-e 结果对象,nfeStatus = Negada |
nfce.canceled | NFC-e 取消(含替代取消)登记成功 | NFC-e 结果对象,nfeStatus = Cancelada |
nfce.cancel_rejected | NFC-e 取消被 SEFAZ 拒绝 | NFC-e 结果对象,nfeStatus = CancelamentoNegado,票仍有效 |
nfce.contingency_issued | SEFAZ 不可用时 NFC-e 已离线开出 | NFC-e 结果对象,nfeStatus = ContingenciaPendente |
nfse.authorized | NFS-e 发行成功 | NFS-e 结果对象,nfseStatus = Autorizada |
nfse.rejected | NFS-e 被发行方驳回,或平台侧失败 | NFS-e 结果对象,nfseStatus = Negada,原因在 nfseMotivoStatus |
nfse.canceled | NFS-e 取消完成(含取消分析准予) | NFS-e 结果对象,nfseStatus = Cancelada |
nfse.cancel_requested | NFS-e 取消分析申请已受理 | NFS-e 结果对象,nfseStatus = EmProcessoDeCancelamento |
nfse.cancel_denied | NFS-e 取消未成功,票仍为已授权 | NFS-e 结果对象,nfseStatus = Autorizada,原因在 nfseMotivoStatus |
nfse.substituted | NFS-e 被替代票取代 | NFS-e 结果对象,nfseStatus = Substituida,多一个 nfseChaveSubstituta |
nfse.inbound.received | 进项 NFS-e 到达,本主体为接收方 / 中介方(需显式订阅) | NFS-e 进项通知 |
nfse.inbound.canceled | 进项 NFS-e 被取消(需显式订阅) | NFS-e 进项通知 |
nfse.inbound.substituted | 进项 NFS-e 被替代(需显式订阅) | NFS-e 进项通知 |
nfse.inbound.manifested | 进项 NFS-e 的表态(确认 / 拒绝)已登记(需显式订阅) | 统一事件体,表态结果在 data |
nfse.inbound.manifest_failed | 进项 NFS-e 表态任务失败,请在默示确认期限前重新表态(需显式订阅) | 统一事件体,失败原因在 data.motivo |
email.sent | 发票邮件已交发信服务(需显式订阅) | 统一事件体,data.status = Enviado |
email.failed | 发票邮件终态失败:等待超时、重试用尽、票已不再授权(需显式订阅) | 统一事件体,data.status = Falha,原因在 data.motivo_falha |
载荷有两种格式,只由事件类型决定(见上表最后一列),与回调地址在控制台配置还是经注册接口登记无关:
- 单据结果对象:无包装的 JSON 对象,首字段为
tipo(NF-e/NFC-e/CT-e/DC-e/NFS-e),字段顺序固定。NF-e 的授权与驳回结果、NFC-e 的全部 5 个事件(nfce.*),以及 CT-e、DC-e、NFS-e 的授权、驳回、取消类结果使用此格式。它没有event_id字段,请按请求头X-Tffiscal-Event-Id去重。 - 统一事件体:
{ "version", "event_id", "event_type", "occurred_at", "data" }。invoice.canceled、invoice.cce.registered、invoice.verify.completed、cte.event.registered、nfse.inbound.*与email.sent/email.failed使用此格式。data内只增不改不删,破坏性变更升version。
注意: SEFAZ 驳回从不作为发行调用的 HTTP 错误返回,而是体现为查询状态
Negada与invoice.rejected/cte.rejected/dce.rejected事件。CT-e 任务在平台侧终局失败(Falha)不发回调,请通过 CT-e 查询 获知。
请求头
| 请求头 | 说明 |
|---|---|
X-Tffiscal-Event | 事件编码,如 invoice.authorized(在控制台保存地址时发出的测试投递为 webhook.verify) |
X-Tffiscal-Event-Id | 事件 id,即幂等键;重试间不变,同一事件的所有目标共用 |
X-Tffiscal-Delivery-Id | 投递标识;不要据此去重,请用事件 id |
X-Tffiscal-Timestamp | Unix 秒,每次尝试重新生成 |
X-Tffiscal-Signature | hex( HMAC-SHA256( app_secret, timestamp + "." + body ) ),小写 |
token | 回调令牌(选填):在控制台设置、或在 Webhook 注册 时提交的值,原样回传;未设置令牌时不带此头 |
x-token | 与 token 同值、同时出现,校验任一即可 |
验签
用你的 app_secret 对字符串 timestamp + "." + rawBody 计算 HMAC-SHA256,并与请求头 X-Tffiscal-Signature 做常量时间比较。必须用收到的原始字节:先反序列化再重序列化会改变字段顺序或空白,导致签名失败。时间戳过旧的投递应拒绝(容忍 5 分钟是合理取值)。
建议校验签名:它既能证明来源,也能证明报文未被篡改。若设置了回调令牌,也可以再比对请求头 token。
Node.js:
const crypto = require('node:crypto');/*** 校验 TF Fiscal webhook 投递。* @param {string} secret 你的 app_secret* @param {string} timestamp 请求头 X-Tffiscal-Timestamp 的值* @param {Buffer|string} rawBody 未解析的原始请求体* @param {string} signature 请求头 X-Tffiscal-Signature 的值* @returns {boolean} 签名有效时返回 true*/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 {/*** 校验 TF Fiscal webhook 投递。** @param secret 你的 app_secret* @param timestamp 请求头 X-Tffiscal-Timestamp 的值* @param rawBody 未解析的原始请求体* @param signature 请求头 X-Tffiscal-Signature 的值* @return 签名有效时返回 true*/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;}}}
NF-e 载荷
事件 invoice.authorized 与 invoice.rejected。通过 Webhook 注册 登记;开票流程见 NF-e。
授权:
{"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"}
驳回:
{"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"}
| 字段 | 类型 | 说明 |
|---|---|---|
tipo | string | 固定 NF-e |
empresaId | string | 主体标识 |
nfeId | string | 开票时传入的 id |
nfeStatus | string | Autorizada / Negada |
nfeMotivoStatus | string | 驳回原因:SEFAZ 返回码 + 描述;授权时无值 |
nfeLinkDanfe | string | DANFE PDF 下载地址(见文件下载;回调时刻即可用);驳回时无值 |
nfeLinkXml | string | 授权 XML 下载地址;驳回时无值 |
nfeNumero | string | 票号 |
nfeSerie | string | 系列号 |
nfeChaveAcesso | string | 44 位访问密钥 |
nfeDataEmissao | string | 开票时间,ISO-8601 UTC |
nfeDataAutorizacao | string | 授权时间;驳回时无值 |
nfeNumeroProtocolo | string | 授权协议号;驳回时无值 |
nfeDigestValue | string | 签名摘要,当前不提供 |
无值的字段以空值形式出现在载荷中。
NF-e 取消与更正函
invoice.canceled 使用统一事件体,data 含 invoice_id(平台发票标识)、chave(44 位访问密钥)与 protocolo(取消事件协议号)。取消本身由 NF-e 取消 同步确认,查询状态随之变为 Cancelada。
invoice.cce.registered 在目录中预留(data:invoice_id / chave / n_seq)。更正函登记接口同步返回协议号,目前不推送该回调。
CT-e 载荷
复用 NF-e 的登记与请求头;发行流程见 CT-e。结果对象 tipo = CT-e,字段顺序固定。
| 事件 | 触发 | cteStatus |
|---|---|---|
cte.authorized | 授权 100 | Autorizada |
cte.rejected | SEFAZ 驳回(cStat 非 100) | Negada(cteMotivoStatus 为 cStat - 原因);任务终态失败(Falha)不发回调,请经查询接口获知 |
cte.canceled | 取消 135 | Cancelada |
cte.event.registered | 其他事件登记成功 | 统一事件体,data 含 event_code / n_seq / protocolo |
授权:
{"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": "..."}
| 字段 | 类型 | 说明 |
|---|---|---|
tipo | string | 固定 CT-e |
empresaId | string | 主体标识 |
cteId | string | 发行时传入的 id |
cteStatus | string | Autorizada / Negada / Cancelada |
cteMotivoStatus | string | null | 驳回时为 cStat - 原因;其余为 null |
cteLinkDacte | string | null | DACTE PDF 下载地址;驳回时为 null |
cteLinkXml | string | null | 授权 XML 下载地址;驳回时为 null |
cteNumero | string | 单据号 |
cteSerie | string | 系列号 |
cteChaveAcesso | string | 44 位访问密钥 |
cteDataEmissao | string | 发行时间,ISO-8601 UTC |
cteDataAutorizacao | string | null | 授权时间;驳回时为 null |
cteNumeroProtocolo | string | null | 授权协议号;驳回时为 null |
cteDigestValue | string | null | 授权 XML 的签名摘要 |
cteLinkDacte 与 cteLinkXml 在授权回调到达时即可用;链接不需要签名头、302 跳转,见文件下载。
事件登记(cte.event.registered),以送达凭证(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"}}
| 字段 | 类型 | 说明 |
|---|---|---|
event_code | string | SEFAZ 事件码,如 110110 更正函、110180 送达凭证 |
n_seq | string | 事件序号 |
protocolo | string | 事件协议号 |
DC-e 载荷
复用 NF-e 的登记与请求头;发行流程见 DC-e。结果对象 tipo = DC-e,14 个字段顺序固定。
| 事件 | 触发 | dceStatus |
|---|---|---|
dce.authorized | 授权 100 | Autorizada |
dce.rejected | SEFAZ 驳回或任务终局失败(含单据编号前的失败,此时无 chave) | Negada(dceMotivoStatus 为 cStat - 原因;终局失败无 cStat 时只有原因) |
dce.canceled | 取消登记成功(135 / 136 / 155) | Cancelada(dceDataAutorizacao 为取消登记时刻、dceNumeroProtocolo 为取消事件协议号) |
dce.cancel_rejected | 取消被 SEFAZ 拒绝或取消任务终局失败 | CancelamentoNegado(单据仍为已授权,带授权协议号 / 摘要 / XML 链接) |
授权:
{"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="}
驳回:
{"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}
单据编号前失败(主体未配置 DC-e、号段缺失、报文映射失败等终局失败,单据未编号、无 chave):同样收到 dce.rejected,单据事实字段为 null,dceMotivoStatus 为失败原因,dceDataEmissao 为受理时刻。请以 dceId 关联,不要假定 dceChaveAcesso 一定有值:
{"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}
取消成功:
{"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}
取消被拒:
{"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="}
| 字段 | 类型 | 说明 |
|---|---|---|
tipo | string | 固定 DC-e |
empresaId | string | 主体标识 |
dceId | string | 发行时传入的 id;所有事件的关联键,单据未编号时也有值 |
dceStatus | string | Autorizada / Negada / Cancelada / CancelamentoNegado |
dceMotivoStatus | string | null | SEFAZ 驳回时为 cStat - 原因;无 cStat 的终局失败只有原因;成功时为 null |
dceLinkDace | string | null | DACE PDF 下载地址;驳回与 CancelamentoNegado 时为 null |
dceLinkXml | string | null | 授权 XML 下载地址;驳回时为 null |
dceNumero | string | null | 单据号;未编号时为 null |
dceSerie | string | null | 系列号;未编号时为 null |
dceChaveAcesso | string | null | 44 位访问密钥;未编号时为 null |
dceDataEmissao | string | 发行时间,ISO-8601 UTC(编号前失败时为受理时刻) |
dceDataAutorizacao | string | null | 授权时间;Cancelada 时为取消登记时刻;驳回时为 null |
dceNumeroProtocolo | string | null | 授权协议号;Cancelada 时为取消事件协议号;驳回时为 null |
dceDigestValue | string | null | 授权 XML 的签名摘要;驳回与 Cancelada 时为 null |
两个链接与查询接口的格式相同(/openapi/files/{kind}/{ref}?token=...),有效期取租户级设置(默认 7 天),见文件下载。
NFC-e 载荷
复用 NF-e 的回调地址与请求头;开票流程见 NFC-e。这 5 个事件不在缺省订阅里,必须在 Webhook 注册的 eventos 里显式列出才会投递。结果对象与 NF-e 字段相同,tipo = NFC-e,另带二维码内容 nfeQrCode,14 个字段顺序固定、值为空时也会出现。
| 事件码 | 触发 | nfeStatus |
|---|---|---|
nfce.authorized | 授权通过,或离线票补传后授权 | Autorizada |
nfce.rejected | SEFAZ 驳回、平台后台处理失败,或离线票补传被拒 | Negada(nfeMotivoStatus 为返回码加原因) |
nfce.canceled | 取消或替代取消登记成功 | Cancelada(nfeDataAutorizacao 为取消登记时刻、nfeNumeroProtocolo 为取消协议号) |
nfce.cancel_rejected | 取消被 SEFAZ 拒绝 | CancelamentoNegado(票仍有效,原因在 nfeMotivoStatus) |
nfce.contingency_issued | SEFAZ 不可用时离线开出 | ContingenciaPendente(带小票链接与 nfeQrCode,无协议号) |
授权:
{"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"}
驳回:
{"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}
| 字段 | 类型 | 说明 |
|---|---|---|
tipo | string | 固定 NFC-e |
empresaId | string | 主体标识 |
nfeId | string | 开票时传入的 id,请以此字段关联回调 |
nfeStatus | string | Autorizada / Negada / Cancelada / CancelamentoNegado / ContingenciaPendente |
nfeMotivoStatus | string | null | 驳回或取消被拒的原因;其他情况为 null |
nfeLinkDanfe | string | null | 80mm 小票 PDF 下载地址;驳回时为 null |
nfeLinkXml | string | null | XML 下载地址;驳回时为 null |
nfeNumero | string | null | 票号 |
nfeSerie | string | null | 系列号 |
nfeChaveAcesso | string | null | 44 位访问密钥 |
nfeDataEmissao | string | null | 开票时间,ISO-8601 UTC |
nfeDataAutorizacao | string | null | 授权时间;Cancelada 时为取消登记时刻 |
nfeNumeroProtocolo | string | null | 授权协议号;Cancelada 时为取消协议号 |
nfeQrCode | string | null | 二维码内容;打印小票时把整串编码成二维码 |
NFS-e 载荷
复用 NF-e 的登记与请求头;开票流程见 NFS-e。结果对象 tipo = NFS-e,字段顺序固定。
| 事件 | 触发 | nfseStatus |
|---|---|---|
nfse.authorized | 发行成功 | Autorizada |
nfse.rejected | 被驳回或平台侧失败 | Negada |
nfse.canceled | 取消完成(含分析准予) | Cancelada |
nfse.cancel_requested | 取消分析申请已受理 | EmProcessoDeCancelamento |
nfse.cancel_denied | 取消未成功,票仍已授权:发行方同步拒绝、出站耗尽、或分析申请被市镇驳回 | Autorizada |
nfse.substituted | 本票被替代票取代;载荷多一个 nfseChaveSubstituta | Substituida |
nfse.inbound.received | 进项 NFS-e 到达(本主体为接收方 / 中介方,需显式订阅) | — |
nfse.inbound.canceled | 进项 NFS-e 被取消(需显式订阅) | — |
nfse.inbound.substituted | 进项 NFS-e 被替代(需显式订阅) | — |
注意: 订阅语义。应用从未登记过任何订阅行时,销项六个事件全推;一旦为应用登记了任何一条订阅(例如订阅进项事件或发票邮件事件),该应用就进入白名单模式,只推显式开启的事件。订阅进项事件时请把需要的销项事件一并开启,否则销项通知会停在「暂存待认领」。
授权:
{"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"}
| 字段 | 类型 | 说明 |
|---|---|---|
tipo | string | 固定 NFS-e |
empresaId | string | 主体标识 |
nfseId | string | 开票时传入的 id;所有事件的关联键,发行失败时也有值 |
nfseStatus | string | Autorizada / Negada / Cancelada / EmProcessoDeCancelamento / Substituida |
nfseMotivoStatus | string | null | 失败时为发行方返回码加描述,或平台侧拒绝原因;nfse.cancel_denied 时为取消结论码加描述;成功时为 null |
nfseLinkDanfse | string | null | DANFSe PDF 下载地址;无值时为 null |
nfseLinkXml | string | null | 官方 XML 原文下载地址;无值时为 null |
nfseNumero | string | null | 发行方分配的票号;未发行成功时为 null |
nfseSerie | string | null | 系列号;未发行成功时为 null |
nfseChaveAcesso | string | null | 50 位访问密钥,由发行方生成;发行失败时为 null |
nfseIdentificacaoDps | string | null | 45 位 DPS 标识;发行失败时唯一可用的对账键,取号前失败时也为 null |
nfseDataEmissao | string | 开票时间,ISO-8601 UTC |
nfseDataAutorizacao | string | null | 授权时间;未授权时为 null |
nfseNumeroProtocolo | string | null | 发行方协议号;发行方不返回时为 null |
nfseAtualizadoEm | string | 本次状态变更的时刻,ISO-8601 UTC |
nfseChaveSubstituta | string | 替代票的 50 位访问密钥,仅 nfse.substituted 出现 |
nfseAtualizadoEm 是本次状态变更的时刻。投递不保证顺序:重试、多实例都可能让旧事件晚于新事件到达。请把它与本地已知时刻比较,旧的直接丢弃,不要按到达顺序覆盖状态。
失败回调里 nfseChaveAcesso 可能为空(发行方从未生成密钥),此时用 nfseIdentificacaoDps 对账。取号前失败的回调连 nfseIdentificacaoDps 都为空,只能靠 nfseId 对账。
取消的窗口规则与分析流程见取消 NFS-e;进项票与表态见进项 NFS-e。
验证结论载荷
二级 SEFAZ 核验落定后,平台向你的 webhook 地址推送 invoice.verify.completed。这是最终结论的唯一推送通道;chave 查询 可作轮询兜底。
{"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)"}}
统一事件体字段:
| 字段 | 类型 | 出现性 | 说明 |
|---|---|---|---|
version | string | 恒有 | 载荷 schema 版本,当前 1.0 |
event_id | string | 恒有 | 事件 id,幂等键,重试间不变 |
event_type | string | 恒有 | invoice.verify.completed |
occurred_at | string | 恒有 | 事件时间,ISO-8601 UTC |
data | object | 恒有 | 结论主体,见下 |
data:
| 字段 | 类型 | 出现性 | 说明 |
|---|---|---|---|
chaveAcesso | string | 恒有 | 被验证发票的 44 位访问密钥,回关联你提交记录的键 |
validationStatus | string | 恒有 | 最终结论:VALIDATED / REJECTED / VALIDATION_ERROR,见 NF-e 验证 |
status | string | null | 可空 | SEFAZ 税务状态:Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida;VALIDATION_ERROR 时为 null(未拿到 SEFAZ 结论) |
cStat | string | null | 可空 | SEFAZ 原始返回码(如 100 已授权、101 已取消);未触达 SEFAZ 时为 null |
xMotivo | string | null | 可空 | SEFAZ 原始返回说明(葡语原文) |
protocolo | object | null | 可空 | 协议对象(numero、digestValue),取自官方记录 |
dataAutorizacao | string | null | 可空 | SEFAZ 授权时间,ISO-8601 UTC |
eventos[] | array | 恒有(可为空) | 该发票已登记的税务事件(取消、更正函);无则为空数组 |
verifiedAt | string | 恒有 | 二级核验完成时间,ISO-8601 UTC |
reason | string | 仅失败时 | 仅 REJECTED / VALIDATION_ERROR 出现;解释结论的英文稳定文案 |
validationStatus | 终态 | 处置 |
|---|---|---|
VALIDATED | 是 | 可放行(发货、结算) |
REJECTED | 是 | 不可放行;reason 说明 SEFAZ 判定(已取消 / 否决 / 作废 / 查无 / 协议不符) |
VALIDATION_ERROR | 是,可重试 | 平台侧核验失败,非发票判定;与其他结论一样 24 小时内复用,请稍后经 XML 验证 带请求头 forceRevalidate: true 重新提交 |
webhook 载荷只含语言无关的枚举值,无 *Description 字段。展示文案由接收方自理。
发票邮件载荷
经发送发票邮件受理的邮件到达终态时,平台推送 email.sent(发信服务已接收)或 email.failed(终态失败)。两个事件都需显式订阅:在 Webhook 注册的 eventos 里列出它们才会投递。收到任一事件后,同一张票才能再次发送。载荷为统一事件体,data 字段名为下划线风格。
{"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:
| 字段 | 类型 | 出现性 | 说明 |
|---|---|---|---|
email_id | string | 恒有 | 受理返回的 emailId,可用于查询发票邮件 |
id | string | 恒有 | 发送时传的请求标识 |
tipo_documento | string | 恒有 | NFe / NFSe / CTe / DCe |
documento_id | string | 恒有 | 开票时传的 id |
chave | string | null | 可空 | 访问密钥,44 位或 50 位;密钥回填前失败时为 null |
destinatarios | array | 恒有 | 实际收件邮箱 |
status | string | 恒有 | Enviado(email.sent)或 Falha(email.failed) |
motivo_falha | string | null | 可空 | 失败原因,自由文本;email.sent 时为 null |
失败示例(圣保罗 NFS-e 等密钥超时):
{"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 表示发信服务接收了邮件,不代表已进入收件人邮箱。模板、附件与状态流转见发票邮件。
重试与熔断
- 返回任意 2xx 即视为投递成功。其他状态码、网络异常或 10 秒内未响应均计为失败。
- 失败的投递在首发之后按退避重试:
1 分钟、5 分钟、30 分钟、2 小时、6 小时
- 共 5 次重试(总计 6 次尝试,跨度约 8.6 小时)。之后可联系平台人工重推。
- 熔断:连续投递失败(默认 10 次)会自动停用该 webhook。停用期间新事件不再为它入队。重新调用 Webhook 注册(或在控制台重新校验并保存地址)即恢复投递并清零失败计数;停用期间产生的事件请联系平台回放。
- 重新登记会在下一次重试时生效。
接收方要求
- 10 秒内返回 2xx。 最佳实践:先持久化原始投递,立即返回 2xx,再异步处理。
- 校验来源:对收到的原始字节复算 HMAC;若设置了回调令牌,再把请求头
token与设置值比对。 - 按事件 id 去重(
X-Tffiscal-Event-Id,统一事件体里另有event_id)。 - 公网地址:可从互联网访问的
http/https绝对地址,最长 512 字符。回环、内网与链路本地地址在登记时会被拒绝。 - 响应测试投递:在控制台保存地址时会发出
X-Tffiscal-Event: webhook.verify的事件(设置了回调令牌时同样带token/x-token),直接返回 2xx 即可,无需业务处理。 - 业务动作以结果字段(
nfeStatus、cteStatus、dceStatus、nfseStatus、validationStatus)为准,切勿只依赖同步接口响应。 - 不要假定可选字段一定存在:
dceChaveAcesso可能为null,reason仅失败时出现,NF-e 的无值字段以空值形式出现。
排障
收不到回调?
确认地址是公网可达的 https/http 地址且在 10 秒内返回 2xx。平台按退避重试,连续失败后停用该 webhook;重新调用注册接口即可恢复投递。
验签总是失败?
最常见原因是先反序列化载荷再重序列化后才计算 HMAC,导致字段顺序或空白改变。务必对收到的原始字节做哈希,并原样拼接请求头 X-Tffiscal-Timestamp 的值。
同一事件收到两次?
这是至少一次投递的正常现象。请按事件 id 去重;投递 id 每个目标不同,不能用于去重。
