Aliquot

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:manage ou *:*) 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, header Idempotent-Replay: true); a mesma key com outro body é 409 IDEMPOTENCY_KEY_REUSED.
  • merchant e splits referenciam contas criadas com POST /api/v1/crypto/accounts (kind MERCHANT ou PARTNER). Sem merchant, a própria conta PLATFORM da organização é a vendedora.
  • Omita network e token (e envie "mode": "test" ou "live") para deixar o comprador escolher no checkout.
  • POST /api/v1/crypto/quote retorna 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.

Nesta página