Skip to content

TEF -- Owem 账户间转账

两个 Owem 账户之间的转账(TEF)。无 BACEN 路由,无手续费,通过 TigerBeetle 立即结算。

端点

POST /api/external/transfers

与 PIX Cash-Out 的区别

方面TEF (/transfers)PIX Cash-Out (/pix/cash-out)
目标 ISPB始终为 37839059(Owem)任意 PSP
响应中的 endToEndId不存在E{ISPB}{YYYYMMDDHHmm}{entropy}
feeAmount始终为 0可能收取手续费
结算通过 TigerBeetle 立即结算通过 SPI/BACEN 异步结算
Webhook 家族tef.transfer.*pix.payout.*

当目标密钥或账户位于 Owem Pay IP 时使用此端点。对于任何其他 PSP,请使用 PIX Cash-Out

请求头

类型必填描述
AuthorizationStringApiKey {client_id}:{client_secret}
Content-TypeStringapplication/json
hmacStringbody 的 HMAC-SHA512 签名(十六进制)-- 参见 HMAC-SHA512
Idempotency-KeyString用于防止重复处理的唯一键(最大 256 字符)

Body 中键的字母顺序

HMAC 验证在比较签名之前,会按字母顺序对 body 的 JSON 重新排序。请使用字母顺序序列化 body 的键,否则 HMAC 验证将以 401 失败。参见 HMAC-SHA512

必需权限

API Key 必须具有 transfer:write 权限。没有该权限,请求返回 403 Forbidden

请求体

接受两种互斥的目标模式:

通用

字段类型必填描述
amountInteger请求中以**分(centavos)**为单位。R$ 1,00 = 100响应中后端以基础单位返回(1 BRL = 10000):发送 100 返回 amount: 10000
descriptionString转账描述(最多 140 个字符)
externalIdString您系统的标识符。trim 后最多 128 字符。仅 a-zA-Z0-9._:- 字符。无效值被静默丢弃(响应中为 null)。

模式 A -- 通过 Owem PIX 密钥指定目标

字段类型必填描述
destinationKeyString目标 Owem 账户的 PIX 密钥
destinationKeyTypeStringCPF | CNPJ | EMAIL | PHONE | EVP

模式 B -- 通过 Owem 分行 + 账户指定目标

字段类型必填描述
destinationAgencyString4 位数字(例如 0001
destinationAccountNumberString目标 Owem 账户号码

互斥模式

在同一请求中发送 destinationKeydestinationAgency 返回 422 destination_ambiguous。两者都不发送返回 422 destination_required

camelCase 或 snake_case

X-Key-Case: camelCase 头存在时,后端自动将 camelCase 转换为 snake_case。您可以在 body 中使用任一格式。规范文档使用 camelCase。

示例(模式 A -- 通过密钥)

bash
curl -X POST https://api.owem.com.br/api/external/transfers \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "hmac: $HMAC" \
  -H "Idempotency-Key: 6f9c2b3e-1d4a-4f8b-9c2d-1e2f3a4b5c6d" \
  -d '{
    "amount": 100,
    "description": "Internal transfer",
    "destinationKey": "62188010000150",
    "destinationKeyType": "CNPJ",
    "externalId": "ord-2026-05-25-001"
  }'

示例(模式 B -- 通过分行+账户)

bash
curl -X POST https://api.owem.com.br/api/external/transfers \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "hmac: $HMAC" \
  -H "Idempotency-Key: 6f9c2b3e-1d4a-4f8b-9c2d-1e2f3a4b5c6d" \
  -d '{
    "amount": 100,
    "description": "Internal transfer",
    "destinationAgency": "0001",
    "destinationAccountNumber": "10001",
    "externalId": "ord-2026-05-25-002"
  }'

计算 HMAC

HMAC-SHA512 签名通过对键按字母顺序排序后序列化的 body JSON 计算得出。完整算法参见 HMAC-SHA512

成功响应 -- 200

对于 amount: 100(R$ 1,00)的请求:

json
{
  "worked": true,
  "final": true,
  "transactionId": "TEFabcd1234...",
  "externalId": "ord-2026-05-25-001",
  "amount": 10000,
  "feeAmount": 0,
  "netAmount": 10000,
  "channel": "tef",
  "status": "settled",
  "detail": "Settled in ledger"
}

响应值以基础单位返回(1 BRL = 10000)

请求接受以分为单位的 amount(R$ 1,00 = 100),但响应返回的 amountfeeAmountnetAmount基础单位为单位(1 BRL = 10000)。要转换为 BRL,请除以 10000(例如,amount: 10000 表示 R$ 1,00)。与 PIX Cash-Out 端点行为相同。

推荐使用 X-Key-Case: camelCase

没有 X-Key-Case: camelCase 头时,响应以 snake_case 返回(transaction_idexternal_idfee_amountnet_amount)。在每个请求中发送该头以接收上面所示的 camelCase。

endToEndId 不存在

TEF 不生成 BACEN End-to-End 标识符。请使用 transactionId(前缀 TEF)或 externalId 来追踪交易。

字段类型描述
workedBooleantrue 表示请求已被接受
finalBooleantrue 当交易已达到终态(已结算的 TEF 始终为 true
transactionIdString交易的唯一标识符(前缀 TEF
externalIdString您的标识符,原样返回。如未提供或被丢弃则为 null
amountInteger转账金额,以基础单位为单位(1 BRL = 10000)。要转换为 BRL,请除以 10000
feeAmountInteger始终为 0(内部 TEF 无手续费)
netAmountInteger等于 amount(无手续费)
channelString始终为 "tef"
statusString立即结算时为 "settled"
detailString描述性消息

错误代码

验证错误(HTTP 422)

响应 shape: {"status": "failed", "errors": [{"code": "<code>", "params": {...}}]}

代码描述
route_via_pix_cashout目标不是 Owem 客户。请使用 POST /api/external/pix/cash-out
destination_required未提供任何目标模式(destinationKey+destinationKeyTypedestinationAgency+destinationAccountNumber)。
destination_ambiguous在同一请求中发送了 destinationKeydestinationAgency
destination_not_found目标密钥/账户在 Owem 中不存在或处于非活动状态。params 包含 account_numberagency
self_transfer源账户与目标账户相同。params.account_id 携带 ID。
pix_key_ambiguous11 位密钥缺少 destinationKeyType,值为 CPF 校验通过且 DDD 有效、第三位为 9(可能是 CPF 或电话)。发送 destinationKeyType: "CPF""PHONE" 以消除歧义。

集成错误(HTTP 400)

响应 shape: {"status": "failed", "errors": [{"code": "<code>", "params": {...}}]}

代码描述
insufficient_balance可用余额小于 amount
pix_out_transaction_limit_exceeded金额超过账户配置的每笔交易限额。

输入错误(HTTP 400)

响应 shape: {"errors": {"bad_request": "<message>"}}

消息原因
invalid or missing amountamount 缺失、零、负数或非整数。

认证 / 授权错误

HTTPShape描述
401{"worked": false, "detail": "Missing HMAC header"}缺少 hmac 头。
401{"worked": false, "detail": "Invalid HMAC signature"}HMAC 签名不匹配(body 顺序错误或 secret 错误)。
401{"error": {"status": 401, "message": "..."}}API Key 缺失、无效、非活动或已过期。查看 error.message
403{"error": {"status": 403, "message": "Request IP not in API key whitelist"}}请求 IP 不在 API Key 白名单中。
403{"errors": {"forbidden": "Permission required: transfer:write"}}API Key 没有 transfer:write 权限。

Webhooks

转账结算时,后端会触发两个 webhook(均为尽力而为)。每个 payload 都会被 dispatcher 自动注入 eventTypestatus: tef.transfer.senttef.transfer.received 收到 status: "settled"; tef.transfer.failed 收到 status: "failed"。货币值以基础单位为单位(1 BRL = 10000),与端点响应一致。

tef.transfer.sent

发送给 caller 的订阅者(源账户)。

json
{
  "eventType": "tef.transfer.sent",
  "status": "settled",
  "transactionId": "TEFabcd1234...",
  "accountId": 10001,
  "senderAccountId": 10001,
  "receiverAccountId": 10002,
  "amount": 10000,
  "description": "Internal transfer",
  "merchantId": "e84b303c-007f-407d-ae20-f1056a24524d",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "settledAt": "2026-05-25T14:30:00Z"
}

tef.transfer.received

发送给目标订阅者(接收账户)。transactionId 带后缀 _RCV 以区分贷方腿和借方腿。

json
{
  "eventType": "tef.transfer.received",
  "status": "settled",
  "transactionId": "TEFabcd1234..._RCV",
  "accountId": 10002,
  "senderAccountId": 10001,
  "receiverAccountId": 10002,
  "amount": 10000,
  "description": "Internal transfer",
  "merchantId": "e84b303c-007f-407d-ae20-f1056a24524d",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "settledAt": "2026-05-25T14:30:00Z"
}

tef.transfer.failed

如果 TigerBeetle 结算失败则触发(罕见)。

json
{
  "eventType": "tef.transfer.failed",
  "status": "failed",
  "accountId": 10001,
  "transactionId": "TEFabcd1234...",
  "receiverAccountId": 10002,
  "amount": 10000,
  "merchantId": "e84b303c-007f-407d-ae20-f1056a24524d",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "failureReason": "tb error description",
  "failedAt": "2026-05-25T14:30:00Z"
}

幂等性

幂等性完全通过 Idempotency-Key 头控制。

  • Header Idempotency-Key(推荐,UUID): 每个请求的唯一键。服务器存储响应 24 小时,任何使用相同键 + 方法 + 路径的重试,**返回缓存的 body 和原始 HTTP 状态码(200 或 202)**以及两个响应头:
    • idempotency-key: <发送的键>
    • x-idempotent-replay: true
  • 服务器在重试时不比较请求 body: 返回的 body 始终是首次成功调用填充缓存的那个。为不同的请求使用不同的键。

Idempotency-Key 不返回 409

与某些集成的预期不同,服务器在键已被使用时不会响应 409。相反,它返回与第一次调用相同的响应(200/202 带原始 body,加上 x-idempotent-replay: true 头)。通过检查该头来检测重试,而不是期望不同的状态码。

clientRequestId 在 body 中不被 TEF 接受

/transfers 端点接受 body 中的 clientRequestId 而不抱怨(后端不拒绝该字段),但不将其用作幂等性。每个具有相同 clientRequestId 但不同 Idempotency-Key 的请求都会产生独立的交易。请专门使用 Idempotency-Key 头来保证幂等性。

下一步

Owem Pay Instituição de Pagamento — ISPB 37839059