Ciclo de vida da transação
Da cotação até a liquidação — todo estado que uma transação BlendFi pode estar, as transições legais entre eles e como reagir a cada um.
Ciclo de vida da transação
Uma transação BlendFi movimenta dinheiro em três passos: seu usuário final paga BRL via Pix, a BlendFi compra o equivalente em USDT numa corretora regulada, e o USDT é liquidado on-chain na carteira que você indicou. Este guia mostra cada estado pelo qual a transação passa, quando o dinheiro vira irreversível e o que fazer em cada parada.
O caminho feliz
cotação ──► transação criada ──► pending_payment
│
▼ (Pix recebido)
payment_received
│
▼ (corretora preencheu)
buying_crypto
│
▼ (envio on-chain)
sending_crypto
│
▼ (minerado)
completedCada seta é um evento determinístico. Não tem polling do seu lado — webhooks anunciam cada transição. No sandbox, você dirige esses eventos sozinho com os test helpers.
Três conceitos
Cotações duram pouco
Uma cotação fixa uma taxa BRL→USDT por uns 30 segundos. Você converte numa transação com `POST /v1/transactions`. Uma vez consumida, fim.
Transações são irreversíveis depois de `buying_crypto`
Até `payment_received`, a gente consegue estornar. Depois que compramos cripto, liquidamos — estornos viram operacionais, não automáticos.
Falhas são explícitas
Toda falha terminal tem um `code` dizendo o motivo. Não existe estado ambíguo de `desconhecido`.
Catálogo de estados
| Estado | Significado | Estado do dinheiro | Próximos estados legais |
|---|---|---|---|
pending_payment | Transação criada; aguardando Pix do usuário final | Sem movimento | payment_received, cancelled, expired, failed |
payment_received | Pix chegou na nossa conta de liquidação | BRL conosco, USDT ainda não | buying_crypto, failed |
buying_crypto | Estamos executando a ordem BRL→USDT na corretora | BRL comprometido, USDT em trânsito | sending_crypto, failed, refunded |
sending_crypto | USDT comprado; transferência on-chain submetida para a carteira de destino | USDT em trânsito | completed, failed |
completed | Transferência on-chain confirmada | USDT entregue | (terminal) |
expired | O usuário final não pagou dentro do TTL do Pix | Sem movimento | (terminal) |
cancelled | Você cancelou antes do pagamento chegar | Sem movimento | (terminal) |
refunded | A gente estornou BRL para o usuário final (após progresso parcial) | BRL devolvido | (terminal) |
failed | Algo quebrou em um dos provedores externos | Veja failure_reason | (terminal) |
Limites de segurança do dinheiro
Onde fica o ponto sem volta
Até e incluindo payment_received, a BlendFi pode estornar o usuário final automaticamente. Quando a transação entra em buying_crypto, o BRL já foi comprometido na corretora — estornos ainda são possíveis (estado refunded) mas são operacionais, não automáticos, e exigem que seu suporte coordene com a gente. Depois de sending_crypto o USDT está on-chain; "estorno" significa devolver USDT para uma carteira remetente, o que é uma transação separada que seu usuário final tem que executar.
Andando pelo caminho feliz
1. Pegue uma cotação
Cotações fixam a taxa por uma janela curta. TTL padrão é 30 segundos.
curl -X POST $BLENDFI_BASE/v1/quotes \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{
"user_id": "01J...",
"source_amount": "100.00",
"source_currency": "BRL",
"target_currency": "USDT",
"destination_wallet_address": "0xabc...",
"destination_wallet_network": "polygon"
}'Resposta:
{
"id": "01JQ...",
"rate": "5.42",
"source_amount": "100.00",
"target_amount": "18.4502",
"expires_at": "2026-04-29T13:00:30Z"
}Se você esperar demais, a cotação retorna 409 quote_expired na hora de consumir. Pega uma nova.
2. Crie a transação
Converta a cotação numa transação. Depois dessa chamada, a transação está em pending_payment e estamos esperando o Pix do usuário final.
curl -X POST $BLENDFI_BASE/v1/transactions \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{"quote_id": "01JQ..."}'Resposta:
{
"id": "01JT...",
"status": "pending_payment",
"pix_qr_code": "00020126...",
"pix_copy_paste": "...",
"expires_at": "2026-04-29T13:30:00Z"
}Mostre o QR Code Pix ou o copia-e-cola para seu usuário final. Ele tem até expires_at (geralmente 30 minutos) para pagar.
3. Usuário final paga via Pix
Quando o Pix chega, a BlendFi muda a transação para payment_received. Você fica sabendo de duas formas:
- Webhook (recomendado): a gente dispara um POST para sua URL de webhook registrada com o novo estado.
- Polling (fallback):
GET /v1/transactions/{id}mostra o estado atual.
{
"id": "01JT...",
"status": "payment_received",
"paid_at": "2026-04-29T13:04:12Z",
...
}4. BlendFi compra USDT e liquida on-chain
Isso é automático. A transação passa por buying_crypto (ordem na corretora) → sending_crypto (envio on-chain) → completed (minerado). Cada transição vira um webhook.
O payload terminal completed inclui o hash da transação on-chain, que você pode mostrar para seu usuário final:
{
"id": "01JT...",
"status": "completed",
"usdt_tx_hash": "0xabc123...",
"completed_at": "2026-04-29T13:08:05Z"
}Modos de falha
Toda falha terminal vem com um failure_reason. As quatro que você realmente vai encontrar:
failure_reason | Em qual estado | O que aconteceu | Sua ação |
|---|---|---|---|
payment_timeout | pending_payment → expired | Usuário final não pagou antes da expiração do Pix | Avise o usuário a tentar de novo com cotação nova |
exchange_rejected | buying_crypto → failed | A corretora não conseguiu preencher a ordem BRL→USDT na taxa fixada | Estorno iniciado automaticamente; entramos em contato |
chain_revert | sending_crypto → failed | A transação on-chain foi revertida (gas, RPC, problema de rede) | Estorno operacional — abra um ticket com o request_id |
kyc_blocked | pending_payment → failed | O status de KYC do usuário mudou no meio do fluxo (revogado, expirado) | Trate na sua UI; o usuário tem que verificar de novo |
Se você ver um estado failed sem failure_reason, trate como internal_error — abra um ticket com o ID da transação.
Dirigindo a máquina de estados no sandbox
O sandbox não aceita pagamentos Pix de verdade nem fala com uma corretora real. Em vez disso, ele oferece endpoints test_helpers que mapeiam 1:1 para transições reais de estado, então sua integração vê a mesma sequência de eventos que veria em produção.
TXID=01JT...
H='-H "Authorization: Bearer $BLENDFI_KEY"'
I='-H "Idempotency-Key: $(uuidgen)"'
# Caminho feliz
curl -X POST $BLENDFI_BASE/v1/test_helpers/transactions/$TXID/pay $H $I
curl -X POST $BLENDFI_BASE/v1/test_helpers/transactions/$TXID/fill-hedge $H $I
curl -X POST $BLENDFI_BASE/v1/test_helpers/transactions/$TXID/send-crypto $H $I
curl -X POST $BLENDFI_BASE/v1/test_helpers/transactions/$TXID/confirm-crypto $H $IVariantes de falha que você pode simular:
| Endpoint | Efeito |
|---|---|
POST /v1/test_helpers/transactions/{id}/expire | Pula a espera do Pix → expired |
POST /v1/test_helpers/transactions/{id}/fail-hedge | Simula exchange_rejected → failed |
POST /v1/test_helpers/transactions/{id}/fail-crypto | Simula chain_revert → failed |
Use isso para validar suas retentativas, exibição de erros e tratamento de estorno antes de tocar em produção.
Webhooks vs polling
Para confiabilidade nível parceiro, registre uma URL de webhook. A gente faz POST de cada transição de estado nela com o corpo completo da transação e um cabeçalho assinado que você pode verificar.
Se um webhook estiver indisponível, a BlendFi tenta de novo com backoff exponencial (1m → 1h → 6h → 24h). Você também pode chamar GET /v1/transactions/{id} a qualquer momento para ler o estado canônico — não tem penalidade de rate-limit para polling de "já terminou?" dentro de limites razoáveis.
Cadência de polling
Polling de uma vez por segundo por transação é tranquilo. Polling a cada 100ms em milhares de transações não é — você vai bater em rate_limit_exceeded. Se você quiser polling mais apertado, é sinal de que precisa de webhook.
O que ler em seguida
Erros e retentativas
O que `invalid_transaction_state` significa em detalhe, e quais erros do ciclo de vida são seguros para retentar.
Idempotência
Toda chamada que dirige estado (`POST /v1/transactions`, todo test helper) exige uma chave de idempotência.
Referência da API
Os formatos exatos de requisição e resposta para os endpoints de transações e cotações.
Fluxo de KYC
Um usuário precisa completar o KYC antes que qualquer transação possa ser criada. O ciclo de vida começa lá.
FAQ
Quanto tempo as cotações duram?
TTL padrão é 30 segundos. O expires_at exato vem na resposta da cotação — não hardcode 30s, leia o campo.
E se o usuário final pagar atrasado, depois da expiração do Pix?
A transação já está expired. O banco do Pix devolve o pagamento atrasado. A gente não cria uma transação nova automaticamente.
Posso cancelar uma transação pending_payment antes do usuário pagar?
Pode. POST /v1/transactions/{id}/cancel (com chave de idempotência) move ela para cancelled. Depois que o Pix chega, cancelar exige fluxo de estorno.
E se a rede on-chain estiver congestionada e a transferência demorar horas?
O estado fica em sending_crypto. A gente retenta com gas mais alto se a transação original ficar travada. Não damos timeout em failed por congestionamento — só por revert explícito ou erro de RPC.
Preenchimentos parciais são possíveis na corretora?
Não. A gente só aceita preenchimentos all-or-nothing. Um preenchimento parcial é tratado como rejeição e a transação vai para failed com failure_reason: exchange_rejected.
Por que existe um payment_received separado e um buying_crypto, em vez de ir direto de um para o outro?
Os dois eventos têm latências diferentes. Pix geralmente liquida em menos de 10 segundos; a ordem na corretora pode levar até um minuto em dias movimentados. Expor separados deixa sua UI mostrar "recebemos seu pagamento, agora convertendo…" — parceiros nos dizem que os usuários finais preferem isso a um spinner ambíguo.
