Implemente um onramp Pix → USDT
Caminho de ponta a ponta para uma integração onramp. Da cotação ao webhook de conclusão.
Este tutorial cobre uma integração onramp completa: o cliente final paga via Pix e a BlendFi liquida USDT no endereço de destino que você controla. Use como ponto de partida para sua implementação.
Pré-requisitos
- Chave de API ativa. Veja Sandbox e chaves.
- Cliente final cadastrado e com KYC de plataforma aprovado. Veja Fluxo de KYC.
- Endereço Polygon que você controla para receber USDT (qualquer wallet funciona no sandbox).
- Endpoint de webhook configurado com verificação de assinatura via header
X-Blendfi-Signature. Veja Verificação de assinatura.
1. Crie a cotação
Sua organização chama POST /v1/quotes com o cliente final, o tipo pix_onramp, o valor em BRL e o endereço de destino on-chain.
curl -X POST $BLENDFI_BASE/v1/quotes \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{
"user_id": "usr_01J...",
"transaction_type": "pix_onramp",
"source_amount": "100.00",
"source_currency": "BRL",
"target_currency": "USDT",
"destination_wallet_address": "0xYOUR_POLYGON_ADDRESS",
"destination_wallet_network": "polygon"
}'const quoteRes = await fetch(`${BASE}/v1/quotes`, {
method: "POST",
headers: { ...auth, "idempotency-key": crypto.randomUUID() },
body: JSON.stringify({
user_id: "usr_01J...",
transaction_type: "pix_onramp",
source_amount: "100.00",
source_currency: "BRL",
target_currency: "USDT",
destination_wallet_address: "0xYOUR_POLYGON_ADDRESS",
destination_wallet_network: "polygon",
}),
});
const quote = await quoteRes.json();quote = requests.post(
f"{BASE}/v1/quotes",
headers={**auth, "idempotency-key": str(uuid.uuid4())},
json={
"user_id": "usr_01J...",
"transaction_type": "pix_onramp",
"source_amount": "100.00",
"source_currency": "BRL",
"target_currency": "USDT",
"destination_wallet_address": "0xYOUR_POLYGON_ADDRESS",
"destination_wallet_network": "polygon",
},
).json()A resposta carrega id, exchange_rate, source_amount, target_amount, expires_at. Mostre target_amount ao cliente final como o valor de USDT que ele vai receber. A cotação vale 15 minutos.
📋 Schema completo em Reference (em breve)
A documentação detalhada do payload aparece aqui em conjunto com a disponibilidade da API. Os campos do exemplo acompanham o desenho conceitual em Cotação.
2. Aceite a cotação e mostre o QR Pix
Confirmação do cliente final: aceite a cotação com POST /v1/quotes/:id/accept. Em uma única chamada atômica, a BlendFi cria a conversão, emite o QR Pix, reserva o limite e abre a janela de 15 minutos.
curl -X POST $BLENDFI_BASE/v1/quotes/$QUOTE_ID/accept \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)"A resposta é a conversão completa, com id, status='awaiting_deposit', pix_qr_code, pix_tx_id, deposit_window_expires_at. Renderize pix_qr_code ao cliente final junto com o valor exato em BRL e o prazo restante.
Recomendações de UX:
- Renderize o QR Pix em destaque com o valor exato em BRL.
- Mostre um contador regressivo até
deposit_window_expires_at. - Ofereça um botão "cancelar" que chama
POST /v1/conversions/:id/cancelenquanto a conversão estiver emawaiting_deposit.
3. Cliente final paga o QR Pix (fora da API)
Em produção, o cliente final escaneia o QR no app do banco e paga. Em sandbox, use o test helper para simular o pagamento.
# Apenas no sandbox
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CONVERSION_ID/simulate-onramp-pix-paid \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)"4. Receba o webhook
A BlendFi entrega um webhook conforme o desfecho. Verifique a assinatura no header X-Blendfi-Signature (formato t=<unix>,v1=<hex>: HMAC-SHA256 sobre {t}.{raw_body}, codificado em hexadecimal).
Eventos possíveis:
conversion.completed: caminho feliz. O Pix foi confirmado e o USDT liquidou no endereço de destino.conversion.failed: erro irrecuperável apósfunded. Payload incluifailure_reason.
Handler mínimo em Node:
import express from "express";
import crypto from "node:crypto";
const app = express();
const SECRET = process.env.BLENDFI_WEBHOOK_SECRET;
app.post(
"/blendfi-webhooks",
express.raw({ type: "application/json" }),
(req, res) => {
const sigHdr = req.header("x-blendfi-signature") ?? "";
const parts = Object.fromEntries(
sigHdr.split(",").map((p) => p.split("=", 2)),
);
const ts = parts.t;
const sig = parts.v1;
const expected = crypto
.createHmac("sha256", SECRET)
.update(`${ts}.${req.body.toString("utf8")}`)
.digest("hex");
if (!sig || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).send("bad signature");
}
res.sendStatus(200);
const event = JSON.parse(req.body.toString("utf8"));
enqueue(event);
},
);Verificação completa em curl, Node e Python: Verificação de assinatura.
Recomendações:
- Trate o handler como idempotente; a mesma entrega pode chegar mais de uma vez.
- Use
X-Blendfi-Event-Idouconversion_idcomo chave de controle de duplicatas.
📋 Schema completo em Reference (em breve)
Os payloads exatos vão na seção de webhooks quando o catálogo de eventos conversion.* for publicado.
5. Caminho feliz (conversion.completed)
Recebeu conversion.completed. O Pix foi confirmado e a BlendFi entregou USDT on-chain no endereço de destino. Mostre confirmação ao cliente final. O payload inclui o hash da transação USDT para sua referência interna ou de auditoria.
Fim do fluxo nominal.
6. Tratamento de erros e idempotência
Idempotency-Keyem todoPOSTmutador. Retentativa segura de uma chamada já bem-sucedida retorna a mesma resposta sem efeitos colaterais.- Handler de webhook idempotente. Use
X-Blendfi-Event-Idouconversion_idcomo chave de controle de duplicatas. - Verificação de assinatura. Rejeite qualquer entrega cuja assinatura não confira.
- Tratamento de lock. Erro de lock no aceite indica que o cliente final já tem outra conversão onramp aberta. Leve a UX a tratar a conversão existente, não a criar uma nova. Veja Lock por usuário e tipo.
- Cancelamento. Se o cliente final desistir antes de pagar, chame
POST /v1/conversions/:id/cancelpara liberar reserva e lock. - Reconciliação. Em incidente, leia
GET /v1/conversions?status=...para reconstruir o estado do que está aberto.
Variantes de falha no sandbox
Os test helpers permitem exercitar caminhos de erro:
# Janela expira sem pagamento
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CONVERSION_ID/expire \
-H "Authorization: Bearer $BLENDFI_KEY" -H "Idempotency-Key: $(uuidgen)"
# Liquidação on-chain falha após o pagamento
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CONVERSION_ID/fail-crypto \
-H "Authorization: Bearer $BLENDFI_KEY" -H "Idempotency-Key: $(uuidgen)"Armadilhas comuns
Reuso de Idempotency-Key
Se você usar a mesma Idempotency-Key para duas operações diferentes, a segunda retorna 409 idempotency_key_reused. Gere um UUID novo por operação lógica.
Polling em vez de webhooks
Não consulte GET /v1/conversions/:id em loop apertado. Os webhooks anunciam toda mudança de estado; polling fica para reconciliação ocasional.
Re-serialização do body no handler de webhook
A maioria dos chamados sobre assinatura inválida vem de middleware re-parseando o body antes da verificação. Use express.raw() (Node) ou request.get_data() (Flask).
Checklist de produção
Antes de migrar de sk_test_… para sk_live_…:
-
Idempotency-Keyem toda chamada mutadora: mesma chave para retentativas, chave nova para operações novas. - Verificação de assinatura de webhook com comparação em tempo constante e janela de replay de 300 s.
- Handler de webhook idempotente: controle de duplicatas pelo
X-Blendfi-Event-Id, resposta em menos de 500 ms. -
request_idcapturado em todos os logs de erro: sua chave para o suporte. - Política de retentativa em
5xxe429com backoff exponencial e a mesmaIdempotency-Key. - Tratamento dos estados de KYC do cliente final:
not_started,pending,approved,rejected,expired. - UX para
conversion.failed: mostre ofailure_reasonao cliente final; o suporte assume a partir daí. - Tratamento de expiração de cotação: countdown na UI; cote de novo no aceite se necessário.
Próximos passos
- Implemente um offramp USDT → Pix: a outra direção.
- Conversão: o mapa completo da máquina de estados.
- Idempotência: a semântica do header
Idempotency-Key. - Erros e retentativas: rede de segurança que sustenta tudo acima.
