TF Fiscal
开发文档

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.authorizedNF-e 被 SEFAZ 授权(cStat 100)NF-e 结果对象,nfeStatus = Autorizada
invoice.rejectedNF-e 被 SEFAZ 驳回NF-e 结果对象,nfeStatus = Negada,原因在 nfeMotivoStatus
invoice.canceledNF-e 取消在 SEFAZ 登记完成(cStat 135)统一事件体,data 含 invoice_id / chave / protocolo
invoice.cce.registeredNF-e 更正函(CC-e)登记完成统一事件体,data 含 invoice_id / chave / n_seq。预留:CC-e 登记目前为同步返回,本事件暂不推送
invoice.verify.completedNF-e 验证得出最终结论(VALIDATED / REJECTED / VALIDATION_ERROR)统一事件体,结论在 data
cte.authorizedCT-e 授权通过(cStat 100)CT-e 结果对象,cteStatus = Autorizada
cte.rejectedCT-e 被 SEFAZ 驳回(cStat 非 100)CT-e 结果对象,cteStatus = Negada,cteMotivoStatus 为 cStat - 原因
cte.canceledCT-e 取消登记完成(cStat 135)CT-e 结果对象,cteStatus = Cancelada
cte.event.registeredCT-e 其他事件登记成功(更正函、送达凭证、送达失败、服务异议及其撤销)统一事件体,data 含 event_code / n_seq / protocolo
dce.authorizedDC-e 授权通过(cStat 100)DC-e 结果对象,dceStatus = Autorizada
dce.rejectedDC-e 被 SEFAZ 驳回,或发行任务终局失败(含单据编号前的失败)DC-e 结果对象,dceStatus = Negada
dce.canceledDC-e 取消登记成功(cStat 135 / 136 / 155)DC-e 结果对象,dceStatus = Cancelada
dce.cancel_rejectedDC-e 取消被 SEFAZ 拒绝,或取消任务终局失败DC-e 结果对象,dceStatus = CancelamentoNegado,单据仍为已授权
nfce.authorizedNFC-e 授权通过;离线票补传授权后也推送NFC-e 结果对象,nfeStatus = Autorizada
nfce.rejectedNFC-e 被 SEFAZ 驳回,或平台后台处理失败;离线票补传被拒也推送NFC-e 结果对象,nfeStatus = Negada
nfce.canceledNFC-e 取消(含替代取消)登记成功NFC-e 结果对象,nfeStatus = Cancelada
nfce.cancel_rejectedNFC-e 取消被 SEFAZ 拒绝NFC-e 结果对象,nfeStatus = CancelamentoNegado,票仍有效
nfce.contingency_issuedSEFAZ 不可用时 NFC-e 已离线开出NFC-e 结果对象,nfeStatus = ContingenciaPendente
nfse.authorizedNFS-e 发行成功NFS-e 结果对象,nfseStatus = Autorizada
nfse.rejectedNFS-e 被发行方驳回,或平台侧失败NFS-e 结果对象,nfseStatus = Negada,原因在 nfseMotivoStatus
nfse.canceledNFS-e 取消完成(含取消分析准予)NFS-e 结果对象,nfseStatus = Cancelada
nfse.cancel_requestedNFS-e 取消分析申请已受理NFS-e 结果对象,nfseStatus = EmProcessoDeCancelamento
nfse.cancel_deniedNFS-e 取消未成功,票仍为已授权NFS-e 结果对象,nfseStatus = Autorizada,原因在 nfseMotivoStatus
nfse.substitutedNFS-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-TimestampUnix 秒,每次尝试重新生成
X-Tffiscal-Signaturehex( HMAC-SHA256( app_secret, timestamp + "." + body ) ),小写
token回调令牌(选填):在控制台设置、或在 Webhook 注册 时提交的值,原样回传;未设置令牌时不带此头
x-token与 token 同值、同时出现,校验任一即可

验签

用你的 app_secret 对字符串 timestamp + "." + rawBody 计算 HMAC-SHA256,并与请求头 X-Tffiscal-Signature 做常量时间比较。必须用收到的原始字节:先反序列化再重序列化会改变字段顺序或空白,导致签名失败。时间戳过旧的投递应拒绝(容忍 5 分钟是合理取值)。

建议校验签名:它既能证明来源,也能证明报文未被篡改。若设置了回调令牌,也可以再比对请求头 token。

Node.js:

javascript
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:

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。

授权:

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"
}

驳回:

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"
}
字段类型说明
tipostring固定 NF-e
empresaIdstring主体标识
nfeIdstring开票时传入的 id
nfeStatusstringAutorizada / Negada
nfeMotivoStatusstring驳回原因:SEFAZ 返回码 + 描述;授权时无值
nfeLinkDanfestringDANFE PDF 下载地址(见文件下载;回调时刻即可用);驳回时无值
nfeLinkXmlstring授权 XML 下载地址;驳回时无值
nfeNumerostring票号
nfeSeriestring系列号
nfeChaveAcessostring44 位访问密钥
nfeDataEmissaostring开票时间,ISO-8601 UTC
nfeDataAutorizacaostring授权时间;驳回时无值
nfeNumeroProtocolostring授权协议号;驳回时无值
nfeDigestValuestring签名摘要,当前不提供

无值的字段以空值形式出现在载荷中。

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授权 100Autorizada
cte.rejectedSEFAZ 驳回(cStat 非 100)Negada(cteMotivoStatus 为 cStat - 原因);任务终态失败(Falha)不发回调,请经查询接口获知
cte.canceled取消 135Cancelada
cte.event.registered其他事件登记成功统一事件体,data 含 event_code / n_seq / protocolo

授权:

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": "..."
}
字段类型说明
tipostring固定 CT-e
empresaIdstring主体标识
cteIdstring发行时传入的 id
cteStatusstringAutorizada / Negada / Cancelada
cteMotivoStatusstring | null驳回时为 cStat - 原因;其余为 null
cteLinkDactestring | nullDACTE PDF 下载地址;驳回时为 null
cteLinkXmlstring | null授权 XML 下载地址;驳回时为 null
cteNumerostring单据号
cteSeriestring系列号
cteChaveAcessostring44 位访问密钥
cteDataEmissaostring发行时间,ISO-8601 UTC
cteDataAutorizacaostring | null授权时间;驳回时为 null
cteNumeroProtocolostring | null授权协议号;驳回时为 null
cteDigestValuestring | null授权 XML 的签名摘要

cteLinkDacte 与 cteLinkXml 在授权回调到达时即可用;链接不需要签名头、302 跳转,见文件下载。

事件登记(cte.event.registered),以送达凭证(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"
}
}
字段类型说明
event_codestringSEFAZ 事件码,如 110110 更正函、110180 送达凭证
n_seqstring事件序号
protocolostring事件协议号

DC-e 载荷

复用 NF-e 的登记与请求头;发行流程见 DC-e。结果对象 tipo = DC-e,14 个字段顺序固定。

事件触发dceStatus
dce.authorized授权 100Autorizada
dce.rejectedSEFAZ 驳回或任务终局失败(含单据编号前的失败,此时无 chave)Negada(dceMotivoStatus 为 cStat - 原因;终局失败无 cStat 时只有原因)
dce.canceled取消登记成功(135 / 136 / 155)Cancelada(dceDataAutorizacao 为取消登记时刻、dceNumeroProtocolo 为取消事件协议号)
dce.cancel_rejected取消被 SEFAZ 拒绝或取消任务终局失败CancelamentoNegado(单据仍为已授权,带授权协议号 / 摘要 / XML 链接)

授权:

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="
}

驳回:

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
}

单据编号前失败(主体未配置 DC-e、号段缺失、报文映射失败等终局失败,单据未编号、无 chave):同样收到 dce.rejected,单据事实字段为 null,dceMotivoStatus 为失败原因,dceDataEmissao 为受理时刻。请以 dceId 关联,不要假定 dceChaveAcesso 一定有值:

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
}

取消成功:

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
}

取消被拒:

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="
}
字段类型说明
tipostring固定 DC-e
empresaIdstring主体标识
dceIdstring发行时传入的 id;所有事件的关联键,单据未编号时也有值
dceStatusstringAutorizada / Negada / Cancelada / CancelamentoNegado
dceMotivoStatusstring | nullSEFAZ 驳回时为 cStat - 原因;无 cStat 的终局失败只有原因;成功时为 null
dceLinkDacestring | nullDACE PDF 下载地址;驳回与 CancelamentoNegado 时为 null
dceLinkXmlstring | null授权 XML 下载地址;驳回时为 null
dceNumerostring | null单据号;未编号时为 null
dceSeriestring | null系列号;未编号时为 null
dceChaveAcessostring | null44 位访问密钥;未编号时为 null
dceDataEmissaostring发行时间,ISO-8601 UTC(编号前失败时为受理时刻)
dceDataAutorizacaostring | null授权时间;Cancelada 时为取消登记时刻;驳回时为 null
dceNumeroProtocolostring | null授权协议号;Cancelada 时为取消事件协议号;驳回时为 null
dceDigestValuestring | 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.rejectedSEFAZ 驳回、平台后台处理失败,或离线票补传被拒Negada(nfeMotivoStatus 为返回码加原因)
nfce.canceled取消或替代取消登记成功Cancelada(nfeDataAutorizacao 为取消登记时刻、nfeNumeroProtocolo 为取消协议号)
nfce.cancel_rejected取消被 SEFAZ 拒绝CancelamentoNegado(票仍有效,原因在 nfeMotivoStatus)
nfce.contingency_issuedSEFAZ 不可用时离线开出ContingenciaPendente(带小票链接与 nfeQrCode,无协议号)

授权:

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"
}

驳回:

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
}
字段类型说明
tipostring固定 NFC-e
empresaIdstring主体标识
nfeIdstring开票时传入的 id,请以此字段关联回调
nfeStatusstringAutorizada / Negada / Cancelada / CancelamentoNegado / ContingenciaPendente
nfeMotivoStatusstring | null驳回或取消被拒的原因;其他情况为 null
nfeLinkDanfestring | null80mm 小票 PDF 下载地址;驳回时为 null
nfeLinkXmlstring | nullXML 下载地址;驳回时为 null
nfeNumerostring | null票号
nfeSeriestring | null系列号
nfeChaveAcessostring | null44 位访问密钥
nfeDataEmissaostring | null开票时间,ISO-8601 UTC
nfeDataAutorizacaostring | null授权时间;Cancelada 时为取消登记时刻
nfeNumeroProtocolostring | null授权协议号;Cancelada 时为取消协议号
nfeQrCodestring | 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本票被替代票取代;载荷多一个 nfseChaveSubstitutaSubstituida
nfse.inbound.received进项 NFS-e 到达(本主体为接收方 / 中介方,需显式订阅)—
nfse.inbound.canceled进项 NFS-e 被取消(需显式订阅)—
nfse.inbound.substituted进项 NFS-e 被替代(需显式订阅)—

注意: 订阅语义。应用从未登记过任何订阅行时,销项六个事件全推;一旦为应用登记了任何一条订阅(例如订阅进项事件或发票邮件事件),该应用就进入白名单模式,只推显式开启的事件。订阅进项事件时请把需要的销项事件一并开启,否则销项通知会停在「暂存待认领」。

授权:

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"
}
字段类型说明
tipostring固定 NFS-e
empresaIdstring主体标识
nfseIdstring开票时传入的 id;所有事件的关联键,发行失败时也有值
nfseStatusstringAutorizada / Negada / Cancelada / EmProcessoDeCancelamento / Substituida
nfseMotivoStatusstring | null失败时为发行方返回码加描述,或平台侧拒绝原因;nfse.cancel_denied 时为取消结论码加描述;成功时为 null
nfseLinkDanfsestring | nullDANFSe PDF 下载地址;无值时为 null
nfseLinkXmlstring | null官方 XML 原文下载地址;无值时为 null
nfseNumerostring | null发行方分配的票号;未发行成功时为 null
nfseSeriestring | null系列号;未发行成功时为 null
nfseChaveAcessostring | null50 位访问密钥,由发行方生成;发行失败时为 null
nfseIdentificacaoDpsstring | null45 位 DPS 标识;发行失败时唯一可用的对账键,取号前失败时也为 null
nfseDataEmissaostring开票时间,ISO-8601 UTC
nfseDataAutorizacaostring | null授权时间;未授权时为 null
nfseNumeroProtocolostring | null发行方协议号;发行方不返回时为 null
nfseAtualizadoEmstring本次状态变更的时刻,ISO-8601 UTC
nfseChaveSubstitutastring替代票的 50 位访问密钥,仅 nfse.substituted 出现

nfseAtualizadoEm 是本次状态变更的时刻。投递不保证顺序:重试、多实例都可能让旧事件晚于新事件到达。请把它与本地已知时刻比较,旧的直接丢弃,不要按到达顺序覆盖状态。

失败回调里 nfseChaveAcesso 可能为空(发行方从未生成密钥),此时用 nfseIdentificacaoDps 对账。取号前失败的回调连 nfseIdentificacaoDps 都为空,只能靠 nfseId 对账。

取消的窗口规则与分析流程见取消 NFS-e;进项票与表态见进项 NFS-e。

验证结论载荷

二级 SEFAZ 核验落定后,平台向你的 webhook 地址推送 invoice.verify.completed。这是最终结论的唯一推送通道;chave 查询 可作轮询兜底。

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)"
}
}

统一事件体字段:

字段类型出现性说明
versionstring恒有载荷 schema 版本,当前 1.0
event_idstring恒有事件 id,幂等键,重试间不变
event_typestring恒有invoice.verify.completed
occurred_atstring恒有事件时间,ISO-8601 UTC
dataobject恒有结论主体,见下

data:

字段类型出现性说明
chaveAcessostring恒有被验证发票的 44 位访问密钥,回关联你提交记录的键
validationStatusstring恒有最终结论:VALIDATED / REJECTED / VALIDATION_ERROR,见 NF-e 验证
statusstring | null可空SEFAZ 税务状态:Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida;VALIDATION_ERROR 时为 null(未拿到 SEFAZ 结论)
cStatstring | null可空SEFAZ 原始返回码(如 100 已授权、101 已取消);未触达 SEFAZ 时为 null
xMotivostring | null可空SEFAZ 原始返回说明(葡语原文)
protocoloobject | null可空协议对象(numero、digestValue),取自官方记录
dataAutorizacaostring | null可空SEFAZ 授权时间,ISO-8601 UTC
eventos[]array恒有(可为空)该发票已登记的税务事件(取消、更正函);无则为空数组
verifiedAtstring恒有二级核验完成时间,ISO-8601 UTC
reasonstring仅失败时仅 REJECTED / VALIDATION_ERROR 出现;解释结论的英文稳定文案
validationStatus终态处置
VALIDATED是可放行(发货、结算)
REJECTED是不可放行;reason 说明 SEFAZ 判定(已取消 / 否决 / 作废 / 查无 / 协议不符)
VALIDATION_ERROR是,可重试平台侧核验失败,非发票判定;与其他结论一样 24 小时内复用,请稍后经 XML 验证 带请求头 forceRevalidate: true 重新提交

webhook 载荷只含语言无关的枚举值,无 *Description 字段。展示文案由接收方自理。

发票邮件载荷

经发送发票邮件受理的邮件到达终态时,平台推送 email.sent(发信服务已接收)或 email.failed(终态失败)。两个事件都需显式订阅:在 Webhook 注册的 eventos 里列出它们才会投递。收到任一事件后,同一张票才能再次发送。载荷为统一事件体,data 字段名为下划线风格。

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:

字段类型出现性说明
email_idstring恒有受理返回的 emailId,可用于查询发票邮件
idstring恒有发送时传的请求标识
tipo_documentostring恒有NFe / NFSe / CTe / DCe
documento_idstring恒有开票时传的 id
chavestring | null可空访问密钥,44 位或 50 位;密钥回填前失败时为 null
destinatariosarray恒有实际收件邮箱
statusstring恒有Enviado(email.sent)或 Falha(email.failed)
motivo_falhastring | null可空失败原因,自由文本;email.sent 时为 null

失败示例(圣保罗 NFS-e 等密钥超时):

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 表示发信服务接收了邮件,不代表已进入收件人邮箱。模板、附件与状态流转见发票邮件。

重试与熔断

  • 返回任意 2xx 即视为投递成功。其他状态码、网络异常或 10 秒内未响应均计为失败。
  • 失败的投递在首发之后按退避重试:
text
1 分钟、5 分钟、30 分钟、2 小时、6 小时
  • 共 5 次重试(总计 6 次尝试,跨度约 8.6 小时)。之后可联系平台人工重推。
  • 熔断:连续投递失败(默认 10 次)会自动停用该 webhook。停用期间新事件不再为它入队。重新调用 Webhook 注册(或在控制台重新校验并保存地址)即恢复投递并清零失败计数;停用期间产生的事件请联系平台回放。
  • 重新登记会在下一次重试时生效。

接收方要求

  1. 10 秒内返回 2xx。 最佳实践:先持久化原始投递,立即返回 2xx,再异步处理。
  2. 校验来源:对收到的原始字节复算 HMAC;若设置了回调令牌,再把请求头 token 与设置值比对。
  3. 按事件 id 去重(X-Tffiscal-Event-Id,统一事件体里另有 event_id)。
  4. 公网地址:可从互联网访问的 http / https 绝对地址,最长 512 字符。回环、内网与链路本地地址在登记时会被拒绝。
  5. 响应测试投递:在控制台保存地址时会发出 X-Tffiscal-Event: webhook.verify 的事件(设置了回调令牌时同样带 token / x-token),直接返回 2xx 即可,无需业务处理。
  6. 业务动作以结果字段(nfeStatus、cteStatus、dceStatus、nfseStatus、validationStatus)为准,切勿只依赖同步接口响应。
  7. 不要假定可选字段一定存在:dceChaveAcesso 可能为 null,reason 仅失败时出现,NF-e 的无值字段以空值形式出现。

排障

收不到回调?

确认地址是公网可达的 https/http 地址且在 10 秒内返回 2xx。平台按退避重试,连续失败后停用该 webhook;重新调用注册接口即可恢复投递。

验签总是失败?

最常见原因是先反序列化载荷再重序列化后才计算 HMAC,导致字段顺序或空白改变。务必对收到的原始字节做哈希,并原样拼接请求头 X-Tffiscal-Timestamp 的值。

同一事件收到两次?

这是至少一次投递的正常现象。请按事件 id 去重;投递 id 每个目标不同,不能用于去重。