Skip to content

TEF -- Transferência entre Contas Owem

Transferência entre duas contas Owem (TEF). Sem rota BACEN, sem tarifa, settlement imediato via TigerBeetle.

Endpoint

POST /api/external/transfers

Diferença vs PIX Cash-Out

AspectoTEF (/transfers)PIX Cash-Out (/pix/cash-out)
ISPB destinoSempre 37839059 (Owem)Qualquer PSP
endToEndId no responseNão existeE{ISPB}{YYYYMMDDHHmm}{entropy}
feeAmountSempre 0Pode ser cobrada tarifa
SettlementImediato via TigerBeetleAssíncrono via SPI/BACEN
Família de webhookstef.transfer.*pix.payout.*

Use este endpoint quando a chave ou a conta destino é da Owem Pay IP. Para qualquer outro PSP, use PIX Cash-Out.

Headers

HeaderTipoObrigatórioDescrição
AuthorizationStringSimApiKey {client_id}:{client_secret}
Content-TypeStringSimapplication/json
hmacStringSimAssinatura HMAC-SHA512 do body (hex) -- veja HMAC-SHA512
Idempotency-KeyStringNãoChave única para evitar processamento duplicado (max 256 chars)

Ordem alfabética das chaves no body

A validação HMAC reorderna o JSON do body em ordem alfabética antes de comparar a assinatura. Serialize o body com as chaves em ordem alfabética ou a verificação HMAC falha com 401. Veja HMAC-SHA512.

Permissão obrigatória

A API Key deve ter a permission transfer:write. Sem ela, a requisição retorna 403 Forbidden.

Request Body

Aceita dois modos de destino mutuamente exclusivos:

Comum

CampoTipoObrigatórioDescrição
amountIntegerSimValor em centavos no request. R$ 1,00 = 100. No response, o backend devolve em unidades base (1 BRL = 10000): enviar 100 retorna amount: 10000.
descriptionStringNãoDescrição da transferência (max 140 caracteres)
externalIdStringNãoIdentificador do seu sistema. Max 128 chars após trim. Apenas caracteres a-zA-Z0-9._:-. Valores inválidos são silenciosamente descartados (null no retorno).

Modo A -- destino por chave PIX Owem

CampoTipoObrigatórioDescrição
destinationKeyStringSimChave PIX da conta Owem destino
destinationKeyTypeStringSimCPF | CNPJ | EMAIL | PHONE | EVP

Modo B -- destino por agência + conta Owem

CampoTipoObrigatórioDescrição
destinationAgencyStringSim4 dígitos (ex: 0001)
destinationAccountNumberStringSimNúmero da conta Owem destino

Modos mutuamente exclusivos

Informar destinationKey E destinationAgency na mesma requisição retorna 422 destination_ambiguous. Não informar nenhum dos dois retorna 422 destination_required.

camelCase ou snake_case

O backend converte automaticamente camelCase para snake_case quando o header X-Key-Case: camelCase está presente. Você pode usar qualquer um dos dois formatos no body. A documentação canônica usa camelCase.

Exemplo (Modo A -- por chave)

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": "Transferencia interna",
    "destinationKey": "62188010000150",
    "destinationKeyType": "CNPJ",
    "externalId": "ord-2026-05-25-001"
  }'

Exemplo (Modo B -- por ag+conta)

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": "Transferencia interna",
    "destinationAgency": "0001",
    "destinationAccountNumber": "10001",
    "externalId": "ord-2026-05-25-002"
  }'

Calculando HMAC

A assinatura HMAC-SHA512 é computada sobre o body JSON serializado com as chaves em ordem alfabética. Veja HMAC-SHA512 para o algoritmo completo.

Resposta de Sucesso -- 200

Para uma requisição com 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"
}

Valores no response em unidades base (1 BRL = 10000)

O request aceita amount em centavos (R$ 1,00 = 100), mas o response devolve amount, feeAmount e netAmount em unidades base (1 BRL = 10000). Para reais, divida por 10000 (ex.: amount: 10000 é R$ 1,00). Mesmo comportamento do endpoint PIX Cash-Out.

Header X-Key-Case: camelCase recomendado

Sem o header X-Key-Case: camelCase, o response vem em snake_case (transaction_id, external_id, fee_amount, net_amount). Envie o header em todas as requisições para receber camelCase como mostrado acima.

endToEndId não está presente

TEF não gera identificador End-to-End BACEN. Use transactionId (prefixo TEF) ou externalId para rastrear a transação.

CampoTipoDescrição
workedBooleantrue indica que a requisição foi aceita
finalBooleantrue quando a transação atingiu estado terminal (sempre true em TEF settled)
transactionIdStringIdentificador único da transação (prefixo TEF)
externalIdStringSeu identificador, retornado tal como enviado. null se não informado ou descartado
amountIntegerValor da transferência em unidades base (1 BRL = 10000). Para reais, divida por 10000.
feeAmountIntegerSempre 0 (TEF interno não tem tarifa)
netAmountIntegerIgual a amount (sem tarifa)
channelStringSempre "tef"
statusString"settled" na liquidação imediata
detailStringMensagem descritiva

Códigos de Erro

Erros de validação (HTTP 422)

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

CódigoDescrição
route_via_pix_cashoutDestino não é cliente Owem. Use POST /api/external/pix/cash-out.
destination_requiredNenhum dos modos de destino informado (destinationKey+destinationKeyType ou destinationAgency+destinationAccountNumber).
destination_ambiguousInformou destinationKey E destinationAgency na mesma requisição.
destination_not_foundChave/conta destino não existe ou está inativa na Owem. params inclui account_number e agency.
self_transferA conta source é igual à conta destino. params.account_id traz o ID.
pix_key_ambiguousChave de 11 dígitos sem destinationKeyType quando o valor é CPF válido com DDD válido e terceiro dígito 9 (pode ser CPF ou telefone). Envie destinationKeyType: "CPF" ou "PHONE".

Erros de integração (HTTP 400)

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

CódigoDescrição
insufficient_balanceSaldo disponível menor que o amount.
pix_out_transaction_limit_exceededValor excede o limite por transação configurado para a conta.

Erros de input (HTTP 400)

Response shape: {"errors": {"bad_request": "<message>"}}

MensagemCausa
invalid or missing amountamount ausente, zero, negativo, ou não-inteiro.

Erros de autenticação / autorização

HTTPShapeDescrição
401{"worked": false, "detail": "Missing HMAC header"}Header hmac ausente.
401{"worked": false, "detail": "Invalid HMAC signature"}Assinatura HMAC não confere (body desordenado ou secret errado).
401{"error": {"status": 401, "message": "..."}}API Key ausente, inválida, inativa ou expirada. Confira a mensagem error.message.
403{"error": {"status": 403, "message": "Request IP not in API key whitelist"}}IP da requisição não está na whitelist da API Key.
403{"errors": {"forbidden": "Permission required: transfer:write"}}API Key sem a permission transfer:write.

Webhooks

Quando a transferência liquida, o backend dispara dois webhooks (ambos best-effort). Todos os payloads recebem automaticamente eventType e status injetados pelo dispatcher: tef.transfer.sent e tef.transfer.received recebem status: "settled"; tef.transfer.failed recebe status: "failed". Valores monetários são em unidades base (1 BRL = 10000), iguais ao response do endpoint.

tef.transfer.sent

Disparado para os assinantes do caller (source account).

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

tef.transfer.received

Disparado para os assinantes do destinatário (receiver account). O transactionId tem sufixo _RCV para diferenciar a perna de crédito da de débito.

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

tef.transfer.failed

Disparado se a settle falhar no TigerBeetle (raro).

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

Idempotência

A idempotência é controlada exclusivamente pelo header Idempotency-Key.

  • Header Idempotency-Key (recomendado, UUID): chave única por requisição. O servidor armazena a resposta por 24h e, em qualquer retry com a mesma chave + mesmo método + mesmo path, retorna o corpo cacheado com o status HTTP original (200 ou 202) e dois headers de resposta:
    • idempotency-key: <chave enviada>
    • x-idempotent-replay: true
  • O servidor não compara o body do request no replay: o body retornado é sempre o da primeira chamada bem-sucedida que populou o cache. Use chaves distintas para requisições distintas.

Idempotency-Key não retorna 409

Diferente do que algumas integrações esperam, o servidor não responde 409 quando a chave já foi usada. Em vez disso, devolve o mesmo response da primeira chamada (200/202 com o body original, mais o header x-idempotent-replay: true). Trate replays olhando para esse header, não esperando um status code diferente.

clientRequestId no body não é honrado em TEF

O endpoint /transfers aceita clientRequestId no body sem reclamar (o backend não rejeita o campo), mas não o usa como idempotência. Cada requisição com o mesmo clientRequestId e Idempotency-Key diferente gera uma transação independente. Use exclusivamente o header Idempotency-Key para garantir idempotência.

Próximos Passos

Owem Pay Instituição de Pagamento — ISPB 37839059