跳到主要内容

策略与规则

策略用于为 PIX 操作配置预防性规则。规则在后台面板(Backoffice)中配置,由 API 自动评估。页面提供的每个板块都会在运行时被评估:PIX Out、PIX In、QR Code、密钥、退款、营业时间与 DICT 限额。

配置层级

策略只有两级:

Tenant(基础) -> Account(覆盖 tenant)

解析以板块为单位:如果 account 定义了 pixOut,则整个 account 的 pixOut 板块生效,tenant 的被忽略。account 未定义的板块从 tenant 继承。两级都未定义的板块根本不会被评估——背后没有一个带隐含默认值的"default"层级。

唯一的例外是 DICT 限额:策略未定义时适用平台级默认值——参见 DICT Lookup 限额

PIX Out 规则

规则按以下顺序评估:

顺序规则rule描述
1Kill SwitchpixOutDisabledenabled: false 拒绝所有出账。它是绝对的 —— 优先于白名单
2白名单收款方文件号在白名单中:放行并跳过以下所有规则
3黑名单cpfCnpjBlacklist收款方文件号在黑名单中则拒绝
4同一持有人sameOwnershipOnly仅允许转账到账户持有人本人的 CPF/CNPJ
5营业时间operatingHours在配置窗口之外拒绝
6单笔限额maxAmount每笔转账的最大金额
7夜间限额nightMaxAmount20:00 至 06:00(BRT)之间的最大金额
8主体类型allowedPersonTypes按文件号长度限制收款方为 PF 或 PJ

白名单会短路评估;其余规则则全部评估:一笔交易可能违反多条规则,响应中的 violations 会全部列出。

两个评估阶段

在 POST 时收款方的文件号未必存在:

  • POST 时(边缘):评估 kill switch、营业时间和金额限额。当模式为 BANK_ACCOUNT(唯一在请求体中携带 documentNumber 的模式)时,也会评估文件号相关规则。
  • 解析收款方之后:在 KEYQRCODE 模式下,只有在 DICT 查询密钥或解码 BR Code 之后才知道文件号。文件号相关规则在该时点评估,且在提交给合作银行之前完成。付款 QR Code 时请求体中amount 的情况同理:金额来自 BR Code,一旦得知即应用限额。

因此,密钥模式下的黑名单违规不会在 POST 响应中返回 422:付款被接受,paymentId 被创建,最终结果为 FAILEDerrorCode: policy_deniedpolicy.violationpix.out.failed webhook 会通报结果。

违规响应(POST 阶段)

HTTP/1.1 422 Unprocessable Entity
{
"errorCode": "policy_denied",
"message": "amount R$ 5000.00 exceeds the per-transaction limit of R$ 1000.00",
"violations": [
{ "rule": "maxAmount", "message": "amount R$ 5000.00 exceeds the per-transaction limit of R$ 1000.00" }
]
}

不是 403(表示缺少权限),也不是 400(请求体是合法的):请求本身正确,只是被租户自己配置的规则拒绝了。

监控模式(NOTIFY_ONLY

pixOut 板块的 violationAction 字段决定如何处理违规:

取值效果
BLOCK(默认)拒绝付款
NOTIFY_ONLY记录违规并发送 policy.violation webhook,但付款照常进行

NOTIFY_ONLY 是正式拦截前的演练:先以监控模式保存规则,在面板与 webhook 中观察几天,确认该规则不会误伤正常流量后再改为 BLOCK

监控模式下违规不会出现在交易时间线中 —— 付款已经放行,会有自己的正常结果(pix_out.completed)。违规记录存在于 webhook 和面板中。

PIX In 规则

收到的 PIX 无法被拒绝

清算行先给账户入账,之后才通知我们。策略被评估时,钱已经到账 —— 不存在可以拒绝的时点。因此本板块没有 kill switch,可选择的只是之后做什么:通知,或退回。

顺序规则rule描述
1白名单付款方文件号在白名单中:接受并跳过以下所有规则
2黑名单(文件号)pixIn.cpfCnpjBlacklist付款方文件号在黑名单中
3同一持有人pixIn.sameOwnershipOnly仅接受来自账户持有人本人 CPF/CNPJ 的 PIX
4单笔限额pixIn.maxAmount每笔收款的最大金额
5主体类型pixIn.allowedPersonTypes限制付款方为 PF 或 PJ
6允许的银行pixIn.allowedBankCodes仅接受来自所列银行的 PIX
7禁止的银行pixIn.blacklistedBankCodes拒绝来自所列银行的 PIX

银行清单同时接受 COMPE 代码(3 位)与 ISPB(8 位),用手头有的即可。当清算行的 webhook 未标明付款方银行时,银行相关规则会被跳过,而不是判定为违规;付款方缺少 CPF/CNPJ 时,文件号相关规则同理。

PIX In 的动作

violationAction效果
ALLOW_AND_NOTIFY(默认)记录违规并发送 policy.violation,入账保留
AUTO_REFUND在通知之外,将全额退回付款方

AUTO_REFUND 会在入账后立即发起一笔普通的 PIX 退款,原因为 unauthorized-transaction。它会作为退款出现在你客户的对账单中且无法撤销 —— 建议先用 ALLOW_AND_NOTIFY 观察哪些规则会触发,再真正退钱。

两种情况下都会投递 pix.in.completed,且早于 policy.violation:入账已经发生,隐瞒它会让你的余额与我们的不一致。发生退款时,会产生常规的 PIX out 事件(pix.out.completedpix.out.failed)。

QR Code 规则

在创建 QR Code(静态与动态)时评估,早于在合作银行创建该 QR。

规则rule描述
创建静态 QRqrCode.canCreateStaticfalse 拒绝创建静态 QR
创建动态 QRqrCode.canCreateDynamicfalse 拒绝创建动态 QR
最小金额qrCode.minAmountQR 的最小金额
最大金额qrCode.maxAmountQR 的最大金额

金额限额仅适用于金额创建的 QR。开放式静态 QR(由付款方在自己的银行 App 中输入金额)在创建时没有可比较的金额,两条金额规则会被跳过。

PIX 密钥规则

在创建密钥(POST /v1/accounts/{accountId}/pix/keys)时评估。

规则rule描述
创建keys.canCreatefalse 拒绝创建新密钥
密钥上限keys.maxKeys账户已有该数量的密钥时拒绝

密钥数量在创建时向合作银行查询。若该查询失败,创建仍会放行:我们这边的不可用不应表现为策略违规。已存在的密钥不会因策略变更而被删除。

退款规则

适用于你在 POST /v1/accounts/{accountId}/pix/out/refund 主动发起的退款,全额或部分均适用。

规则rule描述
Kill switchrefund.disabledenabled: false 拒绝所有通过 API 发起的退款
最大金额refund.maxAmount每笔退款的最大金额

不由你选择的退款不在本板块之内:已接受的 MED 与 PIX In 板块的 AUTO_REFUND 属于义务,租户规则不能覆盖义务。同理,PIX Out 的文件号类规则(黑名单、白名单、同一持有人、主体类型)不适用于退款 —— 退款的收款方就是当初的付款方,并非你的选择。

以上三个板块的拒绝方式

QR Code、密钥与退款始终拦截,没有监控模式。三者都是同步操作,规则评估时还没有发生资金变动,因此拒绝可以放在响应中 —— 422errorCode: policy_denied,格式与 PIX Out 相同。在交回 BR Code、把密钥发布到 DICT 或提交退款之后再通知,等于对既成事实发通知。

营业时间

允许出账的时间窗口。支持跨越午夜的窗口(例如 22:00-06:00)。

字段描述示例
start开始时间06:00
end结束时间22:00
timezone时区(默认 America/Sao_PauloAmerica/Sao_Paulo

只配置了一半的窗口(只有 start 或只有 end)视为未配置,不会拦截任何交易。

DICT Lookup 限额

查询 PIX 密钥有独立限额,每次查询都会评估。超限时返回 429errorCode: dict_lookup_limit_exceeded,并发送 phase: "dict_lookup"policy.violation webhook。

并非所有 429 都是您的配额

同一条路由也会返回 errorCode: partner_rate_limited429,那是清算机构对 CorpX 流量的限流,与本页的各项限额无关——您账户的任何计数器都没有用尽。在断定配额耗尽之前,请先确认 errorCode

向 DICT 询问密钥归属的两种方式都计为一次查询:

  • 显式查询——GET /v1/accounts/{accountId}/pix/key/{pixKey}
  • KEY 模式的 PIX Out(即将启用)——POST /v1/accounts/{accountId}/pix/out/async/bigpix。资金转出前必须先解析密钥,而这个问题问的是同一个 DICT。详见下文「按密钥付款同样消耗额度」。

共有三项彼此独立的绝对计数限额,全部按账户计量(见下文「每一项限额都按账户计算」),且以最严格者为准——任意一项超限都会返回 429

限额窗口全局默认值防范目标
maxLookupsPerDayBRT 自然日(从 00:00 起)50每日查询的绝对上限
maxLookupsPerMinute滚动 60 秒15突发:总量在日额度内,但集中在一瞬间
maxNotFoundPer5min滚动 5 分钟10密钥枚举:盲扫会在日额度耗尽之前产生大量 NOT_FOUND

除此之外还有下文所述的按时间窗口的速率上限。它们始终生效:未声明自有数值的账户会继承全平台的基线值。

maxLookupRatio 已停用

此前还有第四项限额 maxLookupRatio——按最近 24 小时内已完成的转账笔数折算允许的查询次数。它已在 v2.53.0 中移除。它与 24 小时窗口的使用率是同一个计算,但精度更差:只有一个窗口,没有自己的样本下限,需要依赖 50 次查询的全局下限才不会卡住没有转账的账户。窗口机制用十二个时间档做了同样的事。策略请求体中仍然接受该字段,但会被忽略;429 响应中不再出现 ratio 这一拒绝原因。

按时间窗口的速率上限

上述三项限额回答的是多少多快。它们都没有回答 DICT 额度桶真正衡量的问题:您每完成一笔支付要花掉多少次查询,以及您的查询中有多大比例根本没有解析出密钥。一个账户完全可能稳稳待在每日 500 次查询以内,却每笔 PIX 花掉 40 次查询——这是扫描密钥者的特征,而不是付款者的。

每个时间窗口都可以对这两个速率设上限:

字段衡量内容
maxUsageRatio窗口内的查询次数 ÷ 已完成的 PIX Out 笔数
maxFailureRatio窗口内(NOT_FOUND + 被拒绝)÷ 查询次数。是分数而非百分比:0.4 = 40%
minLookups样本下限。窗口内查询次数低于该值时,两条规则都不会拒绝

可用的窗口为 5m15m30m1h3h6h12h24h3d7d14d30d

{
"dictLookup": {
"windows": {
"5m": { "maxFailureRatio": 0.6, "minLookups": 10 },
"1h": { "maxUsageRatio": 6, "maxFailureRatio": 0.4, "minLookups": 50 },
"30d": { "maxUsageRatio": 4, "minLookups": 500 }
}
}
}

minLookups 的存在是为了不让速率规则卡住刚起步的账户:一个只有两次查询、没有任何支付的账户,其使用率是无穷大,若没有该下限就会被永久阻断。

未声明上限的窗口并不等于该窗口被关闭。 它会继承全平台的基线值:目前所有档位的 maxUsageRatio 为 2.2、maxFailureRatio 为 0.3,样本下限则随窗口增大——30 分钟以内的窗口为 20,1 小时至 12 小时为 50,从 24 小时起按每天 500 计(24h 为 500,3d 为 1500,依此类推)。这些数值来自对 14 天真实流量的测算,在该历史区间内没有拦下任何账户。在策略中声明自有数值是为该账户或租户做特化,而不是「开启」这项保护。

当多个窗口同时超限时,响应会引用最短的那个——它是直接原因,也是最先恢复的。429 的消息会说明是哪个窗口、测得多少、上限是多少:

DICT lookup usage too high for this account in the last 1h:
120 lookups for 10 successful transfers (12.0 per transfer); max 6.0
DICT lookup failure rate too high for this account in the last 5m:
18 of 24 lookups did not resolve a key (75%); max 40%

errorCode 仍然是 dict_lookup_limit_exceeded——对已经在处理该错误码的接入方而言没有任何变化。

每一项限额都按账户计算

四项限额始终逐账户计量。不存在按租户汇总的上限:某个账户的查询绝不会计入另一个账户,即使两者属于同一租户。

即便限额配置在租户层级,这一点同样成立。租户策略是一个逐账户套用的模板,而不是共享的额度池:

配置正确理解错误理解
租户策略设 maxLookupsPerDay: 50该租户下每个账户每天可查询 50 次整个租户每天共 50 次,由各账户分摊
租户策略设 windows.24h.maxUsageRatio: 2.2每个账户的速率以该账户自身的转账为基数速率以租户的转账总量为基数

账户级策略存在时仍然优先于租户级策略——两者的差别不在计数单位,只在数值从哪里取。

之所以以账户为单位,是因为账户正是 BACEN 在 DICT 限流桶中计量的对象:突发发生在账户上,处罚也落在账户上。按租户汇总会同时带来两种错误——闲置账户承担了邻居的消耗,而在大租户中,单个账户的超额又会被总量稀释,以致任何限额都不会触发。

按密钥付款同样消耗额度

即将启用

本节描述的是即将生效的行为。目前按密钥付款尚未消耗额度,也不会因此返回 429。正式启用前我们会提前通知。

KEY 模式的 PIX Out 在转出资金前必须知道密钥归属。该解析与显式查询共用同一份额度,因此一笔支付也可能收到 429errorCode: dict_lookup_limit_exceeded)。此时不会创建任何支付:没有 paymentId,没有交易,使用相同 Idempotency-Key 重试依然安全。

24 小时缓存正是避免重复计数的机制。最常见的流程——先查询密钥以展示收款方,用户确认后再付款——只消耗一次查询而非两次:第二次由缓存提供。直接付款而不先查询的,同样只消耗一次。两种情况下,100 笔按密钥支付大约消耗 100 次额度,余下的预算留给独立查询。

BigPix 对整个批次只解析一次密钥,无论金额被拆成多少笔。

支付 POST 有两种结果值得注意:

  • 密钥无效或不存在时,直接在响应中返回错误,不创建支付。这比创建一笔几秒后就失败的支付更好。
  • DICT 不可用(清算机构报错或超时)不会拒绝任何请求:支付继续,密钥仍由清算机构解析,与此前一致。我方或清算机构的不可用既不会导致支付被拒,也不会消耗额度。

使用率的分母

窗口的使用率为 查询次数 ÷ 同一窗口内已完成的转账笔数。分母只计入真正完成的转账,且只计该账户自身的:COMPLETEDREFUNDED(已清算、之后才被退回)。失败、被拒或结果不确定的支付不会抬高上限——否则一直重试失败请求的一方,反而会因每次失败获得更多查询额度。

新账户或闲置账户的分母为零,使用率将变成无穷大。保护这一情形的是窗口的 minLookups:在样本下限以下,两条规则都不会拒绝请求,因此账户可以完成最初几次查询,而不会被一个尚无意义的速率拦下。

在 v2.52.0 之前,这项职责由与 maxLookupRatio 绑定的 50 次/24 小时全局下限承担。它已随该限额一并移除;(bootstrap floor of 50 lookups/24h applied) 这条消息不会再出现。

留空不等于无限制

策略中留空的限额并不等于无限制:此时适用上表中的全局默认值。要真正取消上限必须显式声明(后台管理中的 No limit 选项,会写入负值)。无论如何,突发限额始终生效——取消日额度并不等于允许突发。

什么会消耗额度

日额度(maxLookupsPerDay)与 ratio 统计由接入方主动发起、且确实到达 DICT 的查询:密钥已找到、密钥不存在,以及被 DICT 拒绝的密钥(请求无效,或 DICT 对该查询方本身的限流)。清算机构的技术故障以及已被 429 拒绝的请求不计入——不应因我方不可用而消耗额度,也不应在被拦截后越陷越深。显式查询与 PIX Out 内部的解析适用同一规则。

突发限额则相反:maxLookupsPerMinute 统计全部结果,包括被拒绝的,因为它衡量的是客户施加的压力,而非取得的收益。maxNotFoundPer5min 只统计不存在的密钥。

24 小时缓存

对同一密钥的重复查询由 24 小时缓存提供,且缓存命中不消耗额度。两条路径共用同一份缓存:一次显式查询可服务于随后的支付,而支付内部的解析也可服务于随后的查询。若需强制访问 DICT(数据可能过期、归属可能已变更),请在显式查询中使用 ?noCache=true,但要注意此时该次查询会正常计数。

policy.violation Webhook

每一次违规 —— 无论被拒绝还是仅被通报 —— 都会向订阅方发送 policy.violation 事件。

{
"phase": "edge",
"action": "BLOCK",
"blocked": true,
"accountId": "acc_...",
"identifier": "ORDER-1234",
"mode": "KEY",
"amount": 5000.00,
"violations": [
{ "rule": "maxAmount", "message": "amount R$ 5000.00 exceeds the per-transaction limit of R$ 1000.00" }
],
"counterparty": { "document": "12345678900", "name": "JOHN DOE" }
}
字段描述
phaseedge(请求时)、counterparty(解析收款方之后)、pix_in(入账之后)或 dict_lookup
action按配置为 BLOCKNOTIFY_ONLYALLOW_AND_NOTIFYAUTO_REFUND
blocked操作确实被拒绝时为 truepix_in 恒为 false
violations所有被违反的规则,标识位于 rule
counterparty收款方(PIX Out)或付款方(PIX In),已知时出现

phase: "edge" 中没有 paymentId:该 gate 在支付被创建之前运行,这也正是拒绝可以直接体现在 HTTP 响应中的原因。请使用 identifier 做关联。在工作流内部运行的 counterparty 阶段,paymentId 已存在并包含在载荷中。

phase: "pix_in" 中,载荷以 transactionIdendToEnd 取代 paymentId,并通过 refunded 说明金额是否已退回。

投递、重试与签名细节见 Webhooks 指南。

违规的可见性

  • policy.violation webhook —— 任何板块的每一次违规。
  • 交易时间线GET /v1/transactions/{id}/timeline)—— 当违规导致 PIX Out 被拒时,pix_out.failed 事件包含 errorCode: policy_deniedpolicyViolations 中被违反的规则。若是在边界阶段被拒(此时尚无 paymentId),事件挂在该操作的确定性 workflowId 上,也就是 PIX out 响应中返回的同一个 id。该端点两者皆可接受,此外还支持 endToEndIdtxid 和清算机构的 id。
  • 面板 —— Violations 标签页列出历史记录,区分被拦截、仅被通报与已退回的情况,并按规则汇总数量。
对账单不会显示违规

被策略拒绝的操作从未到达合作银行,而对账单(GET /v1/accounts/{accountId}/statement)反映的是账户中实际发生的事情。违规不会出现在那里。请使用 webhook、时间线或面板。例外是由 AUTO_REFUND 退回的 PIX In:那是一笔真实的资金变动,会作为退款出现在对账单中。

IP 白名单与 MED

真正拦截流量的 IP 白名单在 WAF 中,由 Security 页面配置,并不是策略。MED 相关控制见 争议(MED) 指南。