Quickstart
API key → invoice → checkout hospedado → webhook, contra a API de merchant.
Última atualização 27 de out. de 2026
Tudo a seguir roda contra uma stack local (veja Desenvolvimento local); troque o host pelo do seu deploy. API=http://localhost:3005.
1. Uma API key
A API de merchant aceita tanto a sessão com cookie do dashboard (mais x-organization-id) quanto uma API key da organização (Authorization: Bearer fk_live_…). O tráfego com API key fica registrado no log de requests (GET /api/v1/crypto/requests, dashboard → Developers → Logs).
Crie uma no dashboard (Developers → API keys; o secret é exibido uma única vez) ou pela API com a sua sessão:
curl -X POST "$API/api/v1/organizations/$ORG_ID/api-keys" \
-H 'content-type: application/json' -H "x-organization-id: $ORG_ID" --cookie "$SESSION" \
-d '{"name":"backend","permissions":["crypto-invoices:create","crypto-invoices:read","webhooks:manage"]}'Operadores também podem criar uma a partir do servidor: bun run --cwd apps/server mint:api-key --organization-id=<id> --email=<owner> --name=backend (permissions padrão *:*).
[!WARNING] Uma key que pode fazer payouts (
crypto-payouts:create,crypto-payouts:manageou*:*) exige um step-up TOTP no momento em que é criada (totp_code, 2FA obrigatório). Veja Payouts.
2. Uma invoice
curl -X POST "$API/api/v1/crypto/invoices" \
-H "authorization: Bearer $KEY" -H 'content-type: application/json' \
-H 'idempotency-key: order-77310' \
-d '{
"amount": "100.00", "currency": "USD",
"network": "POLYGON_AMOY", "token": "USDT",
"fee_payer": "CUSTOMER", "markup_percent": 2,
"merchant": "halden_type", "splits": [{ "account": "kerning_weekly", "percent": 10 }],
"title": "Halden Grotesk — Desktop license", "merchant_order_id": "order-77310",
"buyer_email": "[email protected]", "redirect_url": "https://shop.example.com/thanks"
}'Idempotency-Keyé obrigatório. Repetir a request com a mesma key e o mesmo body retorna a mesma invoice (200, headerIdempotent-Replay: true); a mesma key com outro body é409 IDEMPOTENCY_KEY_REUSED.merchantesplitsreferenciam contas criadas comPOST /api/v1/crypto/accounts(kindMERCHANTouPARTNER). Semmerchant, a própria conta PLATFORM da organização é a vendedora.- Omita
networketoken(e envie"mode": "test"ou"live") para deixar o comprador escolher no checkout. POST /api/v1/crypto/quoteretorna o mesmo detalhamento sem criar nada.
A resposta traz id (inv_…), status (PENDING), total ("102.50": 100 + 0,50 % + 2 %), deposit_address, payment_uri (EIP-681), required_confirmations, expires_at e pay_url.
3. O comprador paga
Envie o comprador para pay_url (<CRYPTO_CHECKOUT_BASE_URL>/pay/<id>, padrão APP_URL). O checkout hospedado mostra o valor exato, o QR code e um botão Pay with wallet (EIP-1193: troca ou adiciona a chain, verifica saldos, envia o transfer ERC-20). Ele avança sozinho: PENDING → DETECTED (com contagem de confirmações ao vivo) → PAID, depois o recibo com a barra de split, uma invoice imprimível e o caminho de volta para redirect_url.
Para embutir em vez de redirecionar, o client inclui <CryptoCheckout /> (apps/client/components/aliquot/embed/, demo em /embed); dentro do Telegram, /pay/:id?tg=1 roda como Mini-App.
4. O webhook
curl -X POST "$API/api/v1/organizations/$ORG_ID/webhooks/endpoints" \
-H 'content-type: application/json' -H "authorization: Bearer $KEY" \
-d '{"url":"https://shop.example.com/hooks/aliquot","events":["invoice.paid","invoice.overpaid","refund.claimable"]}'Registrar um endpoint exige webhooks:manage. A resposta inclui o secret do endpoint (exibido uma única vez). Cada entrega é um POST com o envelope { id, type, occurredAt, data } e os headers X-Webhook-Event, X-Webhook-Timestamp e X-Webhook-Signature: t=<unix>,v1=<hex>, onde v1 = HMAC-SHA256(secret, "<t>.<raw body>"):
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody: string, header: string, secret: string): boolean {
const [, t, v1] = header.match(/t=(\d+),v1=([0-9a-f]+)/) ?? [];
if (!t || !v1) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Eventos cripto: invoice.created, invoice.detected, invoice.paid, invoice.overpaid (enviado junto com invoice.paid), invoice.underpaid, invoice.expired, payout.completed, payout.failed, refund.claimable (traz claim_url e buyer_email), refund.completed. Os payloads usam snake_case e strings decimais (data.invoice_id, …). Conclua o pedido ao receber invoice.paid.