Skip to content

TEF -- Transferencia entre Cuentas Owem

Transferencia entre dos cuentas Owem (TEF). Sin ruta BACEN, sin tarifa, liquidacion inmediata via TigerBeetle.

Endpoint

POST /api/external/transfers

Diferencia vs PIX Cash-Out

AspectoTEF (/transfers)PIX Cash-Out (/pix/cash-out)
ISPB destinoSiempre 37839059 (Owem)Cualquier PSP
endToEndId en la respuestaNo existeE{ISPB}{YYYYMMDDHHmm}{entropy}
feeAmountSiempre 0Puede cobrarse tarifa
LiquidacionInmediata via TigerBeetleAsincrona via SPI/BACEN
Familia de webhookstef.transfer.*pix.payout.*

Use este endpoint cuando la clave o cuenta destino es de Owem Pay IP. Para cualquier otro PSP, use PIX Cash-Out.

Headers

HeaderTipoObligatorioDescripcion
AuthorizationStringSiApiKey {client_id}:{client_secret}
Content-TypeStringSiapplication/json
hmacStringSiFirma HMAC-SHA512 del body (hex) -- ver HMAC-SHA512
Idempotency-KeyStringNoClave unica para evitar procesamiento duplicado (max 256 chars)

Orden alfabetico de las claves en el body

La validacion HMAC reordena el JSON del body en orden alfabetico antes de comparar la firma. Serialice el body con las claves en orden alfabetico o la verificacion HMAC falla con 401. Ver HMAC-SHA512.

Permiso obligatorio

La API Key debe tener el permiso transfer:write. Sin el, la solicitud retorna 403 Forbidden.

Request Body

Acepta dos modos de destino mutuamente excluyentes:

Comun

CampoTipoObligatorioDescripcion
amountIntegerSiValor en centavos en el request. R$ 1,00 = 100. En la respuesta el backend devuelve en unidades base (1 BRL = 10000): enviar 100 retorna amount: 10000.
descriptionStringNoDescripcion de la transferencia (max 140 caracteres)
externalIdStringNoIdentificador de su sistema. Max 128 chars despues de trim. Solo caracteres a-zA-Z0-9._:-. Valores invalidos se descartan silenciosamente (null en la respuesta).

Modo A -- destino por clave PIX Owem

CampoTipoObligatorioDescripcion
destinationKeyStringSiClave PIX de la cuenta Owem destino
destinationKeyTypeStringSiCPF | CNPJ | EMAIL | PHONE | EVP

Modo B -- destino por agencia + cuenta Owem

CampoTipoObligatorioDescripcion
destinationAgencyStringSi4 digitos (ej: 0001)
destinationAccountNumberStringSiNumero de la cuenta Owem destino

Modos mutuamente excluyentes

Enviar destinationKey Y destinationAgency en la misma solicitud retorna 422 destination_ambiguous. No enviar ninguno retorna 422 destination_required.

camelCase o snake_case

El backend convierte camelCase a snake_case automaticamente cuando el header X-Key-Case: camelCase esta presente. Puede usar cualquiera de los dos formatos en el body. La documentacion canonica usa camelCase.

Ejemplo (Modo A -- por clave)

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

Ejemplo (Modo B -- por agencia+cuenta)

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

La firma HMAC-SHA512 se calcula sobre el body JSON serializado con las claves en orden alfabetico. Ver HMAC-SHA512 para el algoritmo completo.

Respuesta de Exito -- 200

Para una solicitud con 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 en la respuesta en unidades base (1 BRL = 10000)

La solicitud acepta amount en centavos (R$ 1,00 = 100), pero la respuesta retorna amount, feeAmount y netAmount en unidades base (1 BRL = 10000). Para BRL, divida por 10000 (ej: amount: 10000 es R$ 1,00). Mismo comportamiento del endpoint PIX Cash-Out.

Header X-Key-Case: camelCase recomendado

Sin el header X-Key-Case: camelCase, la respuesta retorna en snake_case (transaction_id, external_id, fee_amount, net_amount). Envie el header en todas las solicitudes para recibir camelCase como se muestra arriba.

endToEndId no esta presente

TEF no genera identificador End-to-End BACEN. Use transactionId (prefijo TEF) o externalId para rastrear la transaccion.

CampoTipoDescripcion
workedBooleantrue indica que la solicitud fue aceptada
finalBooleantrue cuando la transaccion ha alcanzado estado terminal (siempre true en TEF liquidado)
transactionIdStringIdentificador unico de la transaccion (prefijo TEF)
externalIdStringSu identificador, retornado tal como enviado. null si no fue informado o descartado
amountIntegerValor de la transferencia en unidades base (1 BRL = 10000). Para BRL, divida por 10000.
feeAmountIntegerSiempre 0 (TEF interno no tiene tarifa)
netAmountIntegerIgual a amount (sin tarifa)
channelStringSiempre "tef"
statusString"settled" en liquidacion inmediata
detailStringMensaje descriptivo

Codigos de Error

Errores de validacion (HTTP 422)

Shape de la respuesta: {"status": "failed", "errors": [{"code": "<code>", "params": {...}}]}

CodigoDescripcion
route_via_pix_cashoutEl destino no es cliente Owem. Use POST /api/external/pix/cash-out.
destination_requiredNingun modo de destino informado (destinationKey+destinationKeyType o destinationAgency+destinationAccountNumber).
destination_ambiguousEnvio destinationKey Y destinationAgency en la misma solicitud.
destination_not_foundClave/cuenta destino no existe o esta inactiva en Owem. params incluye account_number y agency.
self_transferLa cuenta source es igual a la cuenta destino. params.account_id trae el ID.
pix_key_ambiguousClave de 11 digitos sin destinationKeyType cuando el valor es un CPF valido con DDD valido y tercer digito 9 (puede ser CPF o telefono). Envie destinationKeyType: "CPF" o "PHONE".

Errores de integracion (HTTP 400)

Shape de la respuesta: {"status": "failed", "errors": [{"code": "<code>", "params": {...}}]}

CodigoDescripcion
insufficient_balanceSaldo disponible menor que el amount.
pix_out_transaction_limit_exceededValor excede el limite por transaccion configurado para la cuenta.

Errores de input (HTTP 400)

Shape de la respuesta: {"errors": {"bad_request": "<message>"}}

MensajeCausa
invalid or missing amountamount ausente, cero, negativo, o no-entero.

Errores de autenticacion / autorizacion

HTTPShapeDescripcion
401{"worked": false, "detail": "Missing HMAC header"}Header hmac ausente.
401{"worked": false, "detail": "Invalid HMAC signature"}Firma HMAC no coincide (body desordenado o secret incorrecto).
401{"error": {"status": 401, "message": "..."}}API Key ausente, invalida, inactiva o expirada. Verifique el mensaje error.message.
403{"error": {"status": 403, "message": "Request IP not in API key whitelist"}}IP de la solicitud no esta en la whitelist de la API Key.
403{"errors": {"forbidden": "Permission required: transfer:write"}}API Key sin el permiso transfer:write.

Webhooks

Cuando la transferencia se liquida, el backend dispara dos webhooks (ambos best-effort). Todos los payloads reciben automaticamente eventType y status inyectados por el dispatcher: tef.transfer.sent y tef.transfer.received reciben status: "settled"; tef.transfer.failed recibe status: "failed". Los valores monetarios estan en unidades base (1 BRL = 10000), iguales a la respuesta del endpoint.

tef.transfer.sent

Disparado para los suscriptores del caller (cuenta source).

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 los suscriptores del destinatario (cuenta receiver). El transactionId tiene sufijo _RCV para diferenciar la pierna de credito de la de debito.

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 si la liquidacion falla en 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"
}

Idempotencia

La idempotencia se controla exclusivamente por el header Idempotency-Key.

  • Header Idempotency-Key (recomendado, UUID): clave unica por solicitud. El servidor almacena la respuesta por 24h y, en cualquier reintento con la misma clave + metodo + path, retorna el body cacheado con el status HTTP original (200 o 202) mas dos headers de respuesta:
    • idempotency-key: <clave enviada>
    • x-idempotent-replay: true
  • El servidor no compara el body de la solicitud en el replay: el body retornado es siempre el de la primera llamada exitosa que populo el cache. Use claves distintas para solicitudes distintas.

Idempotency-Key no retorna 409

A diferencia de lo que algunas integraciones esperan, el servidor no responde 409 cuando la clave ya fue usada. En su lugar, devuelve la misma respuesta de la primera llamada (200/202 con el body original, mas el header x-idempotent-replay: true). Detecte replays inspeccionando ese header, no esperando un status code diferente.

clientRequestId en el body no es honrado en TEF

El endpoint /transfers acepta clientRequestId en el body sin reclamar (el backend no rechaza el campo), pero no lo usa como idempotencia. Cada solicitud con el mismo clientRequestId y diferente Idempotency-Key genera una transaccion independiente. Use exclusivamente el header Idempotency-Key para garantizar idempotencia.

Siguientes Pasos

Owem Pay Instituição de Pagamento — ISPB 37839059