Cartão, Pix e boleto sobre o mesmo recurso. Valores sempre em centavos,
respostas sempre em JSON, erros sempre com code legível.
Chave secreta (sk_) no header Authorization: Bearer.
A publicável (pk_) só tokeniza cartão no navegador.
Um POST /v1/charges com valor, método e o cartão ou o token.
Pix e boleto voltam com o instrumento pronto.
Cadastre um endpoint e escute charge.captured. Toda entrega
é assinada e reenviada com backoff.
curl -s https://sua-instancia/v1/charges \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pedido-1001" \
-d '{
"amount": 12990,
"method": "card",
"card": {
"number": "4111111111111111",
"holder_name": "MARIA SOUZA",
"exp_month": 12, "exp_year": 2030, "cvv": "123"
}
}'
{
"id": "ch_06G8J0HKYYHNXT834S6J5A9GXM",
"object": "charge",
"status": "captured",
"method": "card",
"amount": 12990, // R$ 129,90
"currency": "BRL",
"captured_amount": 12990,
"refundable_amount": 12990,
"fee_amount": 427,
"net_amount": 12563,
"risk": { "score": 0, "decision": "approve" },
"acquirer": "sandbox",
"card": { "authorization_code": "669341", "nsu": "418032774" },
"created_at": "2026-09-10T01:49:12.084Z"
}
12990 são R$ 129,90 — a API nunca recebe decimal. Repetir a requisição com a mesma
Idempotency-Key devolve exatamente esta resposta, sem cobrar de novo.
{ "error": {
"code": "card_declined",
"message": "Transacao nao autorizada pelo emissor",
"charge_id": "ch_…",
"decline_code": "generic_decline"
} }
| Código | HTTP | Quando acontece |
|---|---|---|
invalid_request | 400 | corpo inválido ou campo faltando |
unauthorized | 401 | chave ausente, revogada ou do tipo errado |
forbidden | 403 | conta suspensa ou operação restrita |
not_found | 404 | recurso inexistente ou de outro merchant |
card_declined | 402 | emissor recusou — não tente outro adquirente |
insufficient_funds | 402 | saldo insuficiente para saque ou estorno |
risk_blocked | 402 | antifraude negou a transação |
idempotency_conflict | 409 | mesma chave, corpo diferente |
invalid_state | 422 | operação impossível no status atual |
rate_limited | 429 | excedeu o limite — respeite retry-after |
acquirer_error | 502 | todos os adquirentes falharam |
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const esperado = crypto.createHmac('sha256', segredo)
.update(`${t}.${corpoCru}`).digest('hex');
// compare com timingSafeEqual
// e rejeite t fora de ±5min
Header x-oxyvon-signature, formato t=<epoch>,v1=<hmac>.
O timestamp entra na mensagem assinada, então payload capturado não pode ser reenviado.
Responda 2xx rápido e processe depois. O mesmo evento pode chegar duas vezes:
trate por x-oxyvon-event-id.