错误
每个 Liqfy 错误都是一个结构稳定的 JSON 对象——只需针对它编写一次处理逻辑,无需再猜测。每个响应(无论成功还是错误)还会携带 X-Request-Id 头(如果你未发送则自动生成,如果你发送了则原样回显)。
#结构
{
"error": {
"type": "invalid_request_error",
"code": "invalid_request",
"message": "Validation failed",
"details": [
{ "param": "amount", "message": "must be a positive integer" }
]
},
"request_id": "req_01J..."
}| 字段 | 是否总是存在 | 说明 |
|---|---|---|
error.type | 是 | 稳定的类别——取值为 invalid_request_error、authentication_error、permission_error、not_found_error、conflict_error、rate_limit_error、api_error 之一。 |
error.code | 是 | 稳定的 snake_case 机器码。你的处理逻辑应根据它进行分支判断,而不是 message。 |
error.message | 是 | 人类可读的说明。可安全记录到日志;不作为自动化处理的契约。 |
error.param | 恰好一个字段失败时 | 标识该失败字段。若有多个字段失败,则不存在此字段,仅由 error.details 列出它们。 |
error.details | 校验失败时 | 用于多字段校验错误的结构化 { param, message }[]。 |
request_id | 是 | 联系支持团队时请提供此值——它可追踪每一条日志、span 和 Kafka 消息。也会在 X-Request-Id 响应头中回显。 |
兼容性别名: 响应体中除
error对象外,还包含若干扁平的顶层字段——statusCode、message、requestId,以及(存在时)code/fieldErrors——供直接读取这些字段的客户端(包括控制台)使用。请读取嵌套的error对象;扁平别名不保证在未来的主版本中保留。
#状态码对照
#结果未知的收款创建
409 idempotency_outcome_unknown 表示原始创建请求可能已到达支付服务商,但其结果尚未确认。请使用相同的 Idempotency-Key 和请求体,并采用退避策略重试。不要为该收款生成新的键:这可能会产生另一笔支付。
即使过了五分钟或 24 小时,原始操作仍会被保留。重试时,如果已保存的结果可用,则返回该结果;否则返回此错误,而不会再次发起创建。查询不到结果并不能证明该收款从未被创建。如果结果仍然未知,请携带 request_id 联系支持团队。已完成的响应至少保留 24 小时。
#HTTP 状态码
| HTTP | error.type | 建议的客户端操作 |
|---|---|---|
| 200 | — | — |
| 201 | — | 保存返回的 id 并继续。 |
| 400 | invalid_request_error | 修正请求体/请求头后重试。不要盲目重试。 |
| 401 | authentication_error | 确认 Authorization: Bearer 头携带的是正确且有效的密钥。 |
| 403 | permission_error | 被权限范围、反欺诈/频率防护、API 密钥退款尝试(消息标记 REFUND_ADMIN_ONLY)或 IP 白名单拦截。请检查请求或联系支持团队。 |
| 404 | not_found_error | 检查你提供的 id。 |
| 409 | conflict_error | 同一个 Idempotency-Key 用于不同的请求体(idempotency_key_reused)、首次请求仍在处理中(idempotency_key_in_flight),或处于不可取消的状态(payment_not_cancellable)。 |
| 422 | invalid_request_error | 违反业务规则(仍属于请求层面的问题)——阅读 message,修正输入,不要重试。 |
| 429 | rate_limit_error | 退避。使用指数延迟 + 抖动重试;存在 Retry-After 时请遵循它。 |
| 5xx | api_error | 可以使用相同的 Idempotency-Key 安全重试——幂等性可防止产生重复。 |
#常见错误
#400 Bad Request
校验失败——error.code 为 invalid_request,error.details 列出所有失败的字段(仅当恰好一个字段失败时才存在 error.param)。
{
"error": {
"type": "invalid_request_error",
"code": "invalid_request",
"message": "Validation failed",
"details": [
{ "param": "amount", "message": "must be a positive integer" },
{ "param": "payment_method", "message": "Invalid enum value" }
]
},
"request_id": "req_01J..."
}当资金类写操作缺少 Idempotency-Key 头时,也会返回 400。在 POST /v1/charges 和 POST /v1/pix/charges 上,请求头是唯一的传递方式。在 POST /payments 上,请求体字段 idempotencyKey 可作为别名使用——如果两者都发送,则必须一致(不一致会返回 400),并以请求头的值为准。POST /payments/{id}/refund 仅限管理后台使用,因此不会接受来自 API 密钥的任何一种形式。
处理方式 修正请求后重试。切勿循环重试。
#401 Unauthorized
{
"error": {
"type": "authentication_error",
"code": "unauthorized",
"message": "Invalid API key"
},
"request_id": "req_01J..."
}可能的原因:
- 缺少请求头——确保已设置
Authorization: Bearer lq_live_...。 - 密钥已轮换——请检查控制台。
- 空白字符——密钥需精确匹配,不能有首尾空格。
处理方式 核实密钥。如果密钥正确,则它可能已被禁用——请联系支持团队。
#403 Forbidden
欺诈/频率防护会在收款到达服务商之前将其拦截——error.code 为 fraud.*:
{
"error": {
"type": "permission_error",
"code": "fraud.customer_velocity_exceeded",
"message": "Too many payment attempts for this customer in a short window"
},
"request_id": "req_01J..."
}另一个 fraud.* 代码对应账户自身的频率限制(消息:"Too many payment attempts from this account in a short window")。
处理方式 审查该客户/放慢重试速度。只有仍然有效的收款才计入限制——已过期或已取消的收款(例如超时未支付的 Pix 二维码)会被忽略,因此重试这些收款是安全的。如果拦截看起来有误,请携带 request_id 发送邮件至 suporte@liqfy.com.br。
#退款——消息标记 REFUND_ADMIN_ONLY
POST /payments/{id}/refund 会以 403 拒绝所有 API 密钥调用方,无论请求体为何:退款只能从 Liqfy 管理后台发起。这不是一个 code 值——error.code 就是普通的 "forbidden",该标记仅作为消息前缀保留,因此请根据代码判断拒绝情况,仅将前缀作为标记进行匹配(例如 message.startsWith("REFUND_ADMIN_ONLY")):
{
"error": {
"type": "permission_error",
"code": "forbidden",
"message": "REFUND_ADMIN_ONLY: reembolsos devem ser solicitados pelo painel/admin, ..."
},
"request_id": "req_01J..."
}参见 API 参考——退款。
#IP 白名单
如果账户配置了 IP 白名单,来自白名单之外地址的请求会以 403 被拒绝——同样的 permission_error 信封结构,error.code "forbidden"。该检查是核心账户路由(钱包、提现、交易、MED 案件、api-keys 及相关路由)上的按账户防护;支付和 webhook 服务上的资金流转路由(/v1/charges、/v1/pix/charges、/v1/payments、/v1/webhooks)不在其覆盖范围内。请将你的集成所使用的所有出口地址(包括 CI 和故障转移主机)加入白名单,否则请求根本无法到达密钥检查环节。详情及如何更新列表:API 密钥。
#account_suspended
账户已被暂停或封禁:在支持团队解除之前,对收款、支付、提现、钱包和交易的 API 密钥请求都会被拒绝。密钥本身仍然有效——轮换密钥无济于事。
{
"error": {
"type": "permission_error",
"code": "account_suspended",
"message": "Conta suspensa: a API não aceita requisições desta conta. Fale com o suporte."
},
"request_id": "req_01J..."
}处理方式 不要重试。请携带 request_id 发送邮件至 suporte@liqfy.com.br。
#404 Not Found
{
"error": {
"type": "not_found_error",
"code": "not_found",
"message": "Charge ch_a1b2c3d4-0000-0000-0000-000000000009 not found"
},
"request_id": "req_01J..."
}处理方式 核实 id。如果你刚刚创建了该资源,请等待 1–2 秒后重试。
#409 Conflict
{
"error": {
"type": "conflict_error",
"code": "idempotency_key_reused",
"message": "Idempotency key 'ORD-7821' already used with a different request body"
},
"request_id": "req_01J..."
}你重放了一个 Idempotency-Key,但请求体与原始请求不同。Liqfy 拒绝静默覆盖。
另外两个 conflict_error 代码也会返回 409:
error.code | 何时 | 处理方式 |
|---|---|---|
idempotency_key_reused | 相同的键,不同的请求体。 | 使用新的键——常见原因是客户修改购物车后重复使用了同一个订单 id。 |
idempotency_key_in_flight | 使用该键的首次请求仍在处理中。 | 等待其完成(或几秒后重试)——不要更换键。 |
payment_not_cancellable | 对 PROCESSING 收款执行 POST /payments/{id}/cancel。 | 等待收款结算完成,然后改为从管理后台发起退款。 |
#业务规则拒绝
请求结构错误(字段缺失/无效)返回 400、error.type: "invalid_request_error"。某些下游配置错误——例如所请求的 payment_method 和 currency 没有配置服务商——会由内部编排器透传,带有其自身的状态码和 UPPER_SNAKE_CASE 机器码,最常见的是 422:
{
"error": {
"type": "invalid_request_error",
"code": "NO_PROVIDER_CONFIGURED",
"message": "No payment provider is configured for this payment method and currency."
},
"request_id": "req_01J..."
}此类别中收款侧的代码:PROVIDER_UNAVAILABLE、PROVIDER_ERROR、PAYMENT_REJECTED、NO_PROVIDER_CONFIGURED、UNKNOWN_METHOD、MOCK_PROVIDER_IN_PRODUCTION、PAYER_IDENTITY_REQUIRED(当 expected_payer_tax_id 锁定收款且付款人证件不匹配时)。
付款/提现侧的代码:WALLET_NOT_FOUND、WALLET_NOT_SPENDABLE、AMOUNT_BELOW_MINIMUM、AMOUNT_ABOVE_MAXIMUM、DAILY_PAYOUT_LIMIT_EXCEEDED(422)、INSUFFICIENT_BALANCE、WITHDRAWAL_NOT_CANCELLABLE。注意提现路径上的例外:INSUFFICIENT_BALANCE 返回的是 400,而不是 422——请根据 error.code 进行分支判断,而不是状态码。
处理方式 阅读 error.code 和 message。不要重试——修正输入;如果账户本应配置服务商,请联系支持团队。
#429 Too Many Requests
{
"error": {
"type": "rate_limit_error",
"code": "rate_limited",
"message": "Rate limit exceeded"
},
"request_id": "req_01J..."
}此响应上的请求头(来自 Kong 的限流插件,按 API 密钥计算——当前阈值参见 快速入门 §4):
Retry-After: 12
X-RateLimit-Limit-Minute: 5000
X-RateLimit-Remaining-Minute: 0处理方式 退避——如果存在 Retry-After 则遵循它,否则使用带抖动的指数退避。
async function withRetry(fn, attempts = 5) {
for (let i = 0; i < attempts; i++) {
try { return await fn(); }
catch (err) {
if (err.status !== 429 && err.status < 500) throw err;
const base = Math.min(1000 * 2 ** i, 30_000);
const jitter = Math.random() * 0.3 * base;
await new Promise(r => setTimeout(r, base + jitter));
}
}
throw new Error('exhausted retries');
}#5xx ——服务端
{
"error": {
"type": "api_error",
"code": "service_unavailable",
"message": "Upstream unavailable"
},
"request_id": "req_01J..."
}可以安全重试。 使用相同的 Idempotency-Key,这样即使处理实际已成功但响应丢失,也不会产生重复收款。
#Webhook 投递错误(服务器 → 你的端点)
当 Liqfy 无法访问你的 webhook 端点时,失败会显示在 GET /v1/webhooks/deliveries 中:
{
"id": "b2c3d4e5-f6a7-4890-9abc-def012345678",
"status": "FAILED",
"attempts": 3,
"maxAttempts": 15,
"lastStatusCode": 502,
"lastError": "HTTP 502",
"lastResponseBody": "Bad Gateway",
"nextRetryAt": "2026-07-23T16:08:00.000Z"
}lastStatusCode / lastError | 含义 |
|---|---|
2xx | 已投递。不会重试。 |
4xx | 你的端点拒绝了该负载。Liqfy 仍会继续重试,上限为 maxAttempts——如有需要,请修复后重放。 |
5xx | 你的端点已宕机。Liqfy 会以指数退避方式重试。 |
429 / Retry-After | Liqfy 会遵循你的 Retry-After 并重新安排投递。 |
Connection timeout / ETIMEDOUT | 你的端点响应时间超过 30 秒。务必快速返回 200 OK——改为异步排队处理。 |
getaddrinfo ENOTFOUND | 你的域名无法解析。请更新已注册的 URL。 |
self signed certificate | TLS 错误。Liqfy 要求有效的公共证书。 |
达到 maxAttempts(默认 15)后,该投递会被发送到 DLQ,并可手动重放。完整的重试/DLQ 语义参见 Webhooks。
#Webhook 管理错误
/v1/webhooks 管理路由上的代码(根据代码不同,为 permission_error / conflict_error / rate_limit_error):
error.code | 何时 |
|---|---|
WEBHOOK_ENDPOINT_DUPLICATE | 该 URL 已在此账户上注册(409)。 |
WEBHOOK_ENDPOINT_LIMIT | 账户已达到活跃端点数量上限。 |
WEBHOOK_HTTPS_REQUIRED | 在 sandbox/staging 之外使用了非 https:// URL。 |
WEBHOOK_OWN_DOMAIN | 该 URL 指向 Liqfy 域名。 |
WEBHOOK_TEST_RATE_LIMIT | 一小时内 POST /endpoints/{id}/test 调用次数过多(429)。 |
WEBHOOK_ROLE_REQUIRED | 调用方的角色无权管理端点(access.canManage: false)。 |
IMPERSONATION_VIEW_ONLY | 当前会话处于模拟登录状态,只能读取。 |
TENANT_NOT_ACTIVE | 账户未激活。 |
#解读 error.details
error.details 是一个由 { param, message } 组成的扁平数组——可直接渲染:
{error.details.map(({ param, message }) => (
<li key={param}>{param}: {message}</li>
))}fieldErrors 别名包含相同的信息,只是重组为 { field: reasons[] } 的形式:
{
"fieldErrors": {
"amount": ["must be a positive integer"]
}
}#何时联系支持团队
在以下情况下,请联系 suporte@liqfy.com.br(或提交工单):
- 5xx 错误持续超过 5 分钟
- 在
maxAttempts之后,webhook 投递仍卡在FAILED状态 - 意外收到
403 Forbidden - 相同的
Idempotency-Key在不同调用中返回不同的收款(这是一个 bug——绝不应发生)
务必提供 request_id——它让我们能通过一次查询追踪每一条日志、span 和 Kafka 消息。
#实体读取限制
HTTP 429,error.type: rate_limit_error,error.code: entity_rate_limited。再次读取该实体前,请等待 Retry-After 指示的秒数。参见 轮询与限制。