策略与规则
策略用于为 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 | 描述 |
|---|---|---|---|
| 1 | Kill Switch | pixOutDisabled | enabled: false 拒绝所有出账。它是绝对的 —— 优先于白名单 |
| 2 | 白名单 | — | 收款方文件号在白名单中:放行并跳过以下所有规则 |
| 3 | 黑名单 | cpfCnpjBlacklist | 收款方文件号在黑名单中则拒绝 |
| 4 | 同一持有人 | sameOwnershipOnly | 仅允许转账到账户持有人本人的 CPF/CNPJ |
| 5 | 营业时间 | operatingHours | 在配置窗口之外拒绝 |
| 6 | 单笔限额 | maxAmount | 每笔转账的最大金额 |
| 7 | 夜间限额 | nightMaxAmount | 20:00 至 06:00(BRT)之间的最大金额 |
| 8 | 主体类型 | allowedPersonTypes | 按文件号长度限制收款方为 PF 或 PJ |
白名单会短路评估;其余规则则全部评估:一笔交易可能违反多条规则,响应中的 violations 会全部列出。
两个评估阶段
在 POST 时收款方的文件号未必存在:
- POST 时(边缘):评估 kill switch、营业时间和金额限额。当模式为
BANK_ACCOUNT(唯一在请求体中携带documentNumber的模式)时,也会评估文件号相关规则。 - 解析收款方之后:在
KEY与QRCODE模式下,只有在 DICT 查询密钥或解码 BR Code 之后才知道文件号。文件号相关规则在该时点评估,且在提交给合作银行之前完成。付款 QR Code 时请求体中未带amount的情况同理:金额来自 BR Code,一旦得知即应用限额。
因此,密钥模式下的黑名单违规不会在 POST 响应中返回 422:付款被接受,paymentId 被创建,最终结果为 FAILED 且 errorCode: policy_denied。policy.violation 与 pix.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 规则
清算行先给账户入账,之后才通知我们。策略被评估时,钱已经到账 —— 不存在可以拒绝的时点。因此本板块没有 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.completed 或 pix.out.failed)。
QR Code 规则
在创建 QR Code(静态与动态)时评估,早于在合作银行创建该 QR。
| 规则 | rule | 描述 |
|---|---|---|
| 创建静态 QR | qrCode.canCreateStatic | false 拒绝创建静态 QR |
| 创建动态 QR | qrCode.canCreateDynamic | false 拒绝创建动态 QR |
| 最小金额 | qrCode.minAmount | QR 的最小金额 |
| 最大金额 | qrCode.maxAmount | QR 的最大金额 |
金额限额仅适用于带金额创建的 QR。开放式静态 QR(由付款方在自己的银行 App 中输入金额)在创建时没有可比较的金额,两条金额规则会被跳过。
PIX 密钥规则
在创建密钥(POST /v1/accounts/{accountId}/pix/keys)时评估。
| 规则 | rule | 描述 |
|---|---|---|
| 创建 | keys.canCreate | false 拒绝创建新密钥 |
| 密钥上限 | keys.maxKeys | 账户已有该数量的密钥时拒绝 |
密钥数量在创建时向合作银行查询。若该查询失败,创建仍会放行:我们这边的不可用不应表现为策略违规。已存在的密钥不会因策略变更而被删除。
退款规则
适用于你在 POST /v1/accounts/{accountId}/pix/out/refund 主动发起的退款,全额或部分均适用。
| 规则 | rule | 描述 |
|---|---|---|
| Kill switch | refund.disabled | enabled: false 拒绝所有通过 API 发起的退款 |
| 最大金额 | refund.maxAmount | 每笔退款的最大金额 |
不由你选择的退款不在本板块之内:已接受的 MED 与 PIX In 板块的 AUTO_REFUND 属于义务,租户规则不能覆盖义务。同理,PIX Out 的文件号类规则(黑名单、白名单、同一持有人、主体类型)不适用于退款 —— 退款的收款方就是当初的付款方,并非你的选择。
以上三个板块的拒绝方式
QR Code、密钥与退款始终拦截,没有监控模式。三者都是同步操作,规则评估时还没有发生资金变动,因此拒绝可以放在响应中 —— 422 加 errorCode: policy_denied,格式与 PIX Out 相同。在交回 BR Code、把密钥发布到 DICT 或提交退款之后再通知,等于对既成事实发通知。
营业时间
允许出账的时间窗口。支持跨越午夜的窗口(例如 22:00-06:00)。
| 字段 | 描述 | 示例 |
|---|---|---|
start | 开始时间 | 06:00 |
end | 结束时间 | 22:00 |
timezone | 时区(默认 America/Sao_Paulo) | America/Sao_Paulo |
只配置了一半的窗口(只有 start 或只有 end)视为未配置,不会拦截任何交易。
DICT Lookup 限额
查询 PIX 密钥有独立限额,每次查询都会评估。超限时返回 429,errorCode: dict_lookup_limit_exceeded,并发送 phase: "dict_lookup" 的 policy.violation webhook。
429 都是您的配额同一条路由也会返回 errorCode: partner_rate_limited 的 429,那是清算机构对 CorpX 流量的限流,与本页的各项限额无关——您账户的任何计数器都没有用尽。在断定配额耗尽之前,请先确认 errorCode。
向 DICT 询问密钥归属的两种方式都计为一次查询:
- 显式查询——
GET /v1/accounts/{accountId}/pix/key/{pixKey}。 KEY模式的 PIX Out(即将启用)——POST /v1/accounts/{accountId}/pix/out、/async与/bigpix。资金转出前必须先解析密钥,而这个问题问的是同一个 DICT。详见下文「按密钥付款同样消耗额度」。
共有三项彼此独立的绝对计数限额,全部按账户计量(见下文「每一项限额都按账户计算」),且以最严格者为准——任意一项超限都会返回 429:
| 限额 | 窗口 | 全局默认值 | 防范目标 |
|---|---|---|---|
maxLookupsPerDay | BRT 自然日(从 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 | 样本下限。窗口内查询次数低于该值时,两条规则都不会拒绝 |
可用的窗口为 5m、15m、30m、1h、3h、6h、12h、24h、3d、7d、14d 和 30d。
{
"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 次 | |
租户策略设 windows.24h.maxUsageRatio: 2.2 | 每个账户的速率以该账户自身的转账为基数 |
账户级策略存在时仍然优先于租户级策略——两者的差别不在计数单位,只在数值从哪里取。
之所以以账户为单位,是因为账户正是 BACEN 在 DICT 限流桶中计量的对象:突发发生在账户上,处罚也落在账户上。按租户汇总会同时带来两种错误——闲置账户承担了邻居的消耗,而在大租户中,单个账户的超额又会被总量稀释,以致任何限额都不会触发。
按密钥付款同样消耗额度
本节描述的是即将生效的行为。目前按密钥付款尚未消耗额度,也不会因此返回 429。正式启用前我们会提前通知。
KEY 模式的 PIX Out 在转出资金前必须知道密钥归属。该解析与显式查询共用同一份额度,因此一笔支付也可能收到 429(errorCode: dict_lookup_limit_exceeded)。此时不会创建任何支付:没有 paymentId,没有交易,使用相同 Idempotency-Key 重试依然安全。
24 小时缓存正是避免重复计数的机制。最常见的流程——先查询密钥以展示收款方,用户确认后再付款——只消耗一次查询而非两次:第二次由缓存提供。直接付款而不先查询的,同样只消耗一次。两种情况下,100 笔按密钥支付大约消耗 100 次额度,余下的预算留给独立查询。
BigPix 对整个批次只解析一次密钥,无论金额被拆成多少笔。
支付 POST 有两种结果值得注意:
- 密钥无效或不存在时,直接在响应中返回错误,不创建支付。这比创建一笔几秒后就失败的支付更好。
- DICT 不可用(清算机构报错或超时)不会拒绝任何请求:支付继续,密钥仍由清算机构解析,与此前一致。我方或清算机构的不可用既不会导致支付被拒,也不会消耗额度。
使用率的分母
窗口的使用率为 查询次数 ÷ 同一窗口内已完成的转账笔数。分母只计入真正完成的转账,且只计该账户自身的:COMPLETED 与 REFUNDED(已清算、之后才被退回)。失败、被拒或结果不确定的支付不会抬高上限——否则一直重试失败请求的一方,反而会因每次失败获得更多查询额度。
新账户或闲置账户的分母为零,使用率将变成无穷大。保护这一情形的是窗口的 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" }
}
| 字段 | 描述 |
|---|---|
phase | edge(请求时)、counterparty(解析收款方之后)、pix_in(入账之后)或 dict_lookup |
action | 按配置为 BLOCK、NOTIFY_ONLY、ALLOW_AND_NOTIFY 或 AUTO_REFUND |
blocked | 操作确实被拒绝时为 true;pix_in 恒为 false |
violations | 所有被违反的规则,标识位于 rule |
counterparty | 收款方(PIX Out)或付款方(PIX In),已知时出现 |
在 phase: "edge" 中没有 paymentId:该 gate 在支付被创建之前运行,这也正是拒绝可以直接体现在 HTTP 响应中的原因。请使用 identifier 做关联。在工作流内部运行的 counterparty 阶段,paymentId 已存在并包含在载荷中。
在 phase: "pix_in" 中,载荷以 transactionId 与 endToEnd 取代 paymentId,并通过 refunded 说明金额是否已退回。
投递、重试与签名细节见 Webhooks 指南。
违规的可见性
policy.violationwebhook —— 任何板块的每一次违规。- 交易时间线(
GET /v1/transactions/{id}/timeline)—— 当违规导致 PIX Out 被拒时,pix_out.failed事件包含errorCode: policy_denied与policyViolations中被违反的规则。若是在边界阶段被拒(此时尚无paymentId),事件挂在该操作的确定性workflowId上,也就是 PIX out 响应中返回的同一个 id。该端点两者皆可接受,此外还支持endToEndId、txid和清算机构的 id。 - 面板 —— Violations 标签页列出历史记录,区分被拦截、仅被通报与已退回的情况,并按规则汇总数量。
被策略拒绝的操作从未到达合作银行,而对账单(GET /v1/accounts/{accountId}/statement)反映的是账户中实际发生的事情。违规不会出现在那里。请使用 webhook、时间线或面板。例外是由 AUTO_REFUND 退回的 PIX In:那是一笔真实的资金变动,会作为退款出现在对账单中。
真正拦截流量的 IP 白名单在 WAF 中,由 Security 页面配置,并不是策略。MED 相关控制见 争议(MED) 指南。