KaironPay API

Documentação completa da API de pagamentos PIX. Integre cobranças, saques, webhooks e muito mais em minutos.

Base URL: https://api.kaironpay.com/api/v1/client

Cobranças PIX

QR Code dinâmico, confirmação em segundos

Saques PIX

Transferências para qualquer chave PIX

Webhooks

Notificações em tempo real por evento

Saldo

Disponível, pendente e reservado

Extrato

Histórico completo de transações

Multi-Provedor

XPayTech, FluxPay, Woovi e mais

Valores monetários (entrada): Todos os valores enviados nas requisições (campo value) são em centavos (integer). Ex: R$ 10,00 = 1000. R$ 1,50 = 150.
Valores monetários (resposta) — atenção à unidade: A maioria dos endpoints retorna valores em centavos (cobranças, transações, saldo e a lista de saques). Porém POST /payouts, GET /payouts/:reference_code e POST /transfers/internal retornam amount/net_amount/fee em reais (decimal). Confira a unidade em cada endpoint.
Autenticação: Todas as requisições exigem o header Authorization: Bearer sk_suachave. Veja a seção Autenticação para detalhes.

Códigos de Status HTTP

CódigoSignificado
200Sucesso
201Recurso criado com sucesso
400Dados inválidos, campos obrigatórios ausentes, saldo insuficiente ou chave PIX inválida
401Não autenticado — API Key inválida ou ausente
403Sem permissão — conta bloqueada ou saques desabilitados
404Recurso não encontrado
409Conflito — correlationID já utilizado em outra operação
500Erro interno — contate o suporte

Status de cobranças

StatusDescrição
ACTIVECobrança criada, aguardando pagamento
COMPLETEDPagamento PIX confirmado e processado
EXPIREDCobrança expirou sem pagamento
REFUNDEDPagamento foi estornado ao pagador
CANCELLEDCobrança cancelada manualmente

Início Rápido

Do zero à primeira cobrança paga em menos de 10 minutos. Siga os passos abaixo.

Guia passo a passo

  • Obtenha sua API Key

    Acesse app.kaironpay.com/dashboard → Configurações → API Key. Copie a chave no formato sk_*. Guarde-a em uma variável de ambiente — nunca hardcode no código.

  • Crie uma cobrança PIX

    Faça um POST /charges com o valor em centavos. A API retorna um brCode (copia-e-cola) e um QR Code em base64 pronto para exibir.

  • Exiba o QR Code ao usuário

    Use o qrCodeImage (base64 PNG) direto em uma tag <img>, ou o brCode num input copiável. Opcionalmente redirecione para o paymentLinkUrl.

  • Configure um webhook

    Cadastre sua URL no painel (Configurações → Webhooks). Quando o pagamento for confirmado, o KaironPay envia TRANSACTION_COMPLETED para a sua URL via POST.

  • Receba o webhook e libere o produto

    No seu handler, verifique o X-Webhook-Token, leia o evento e, se for PayInCompleted, libere o produto/serviço ao cliente.

Fluxo completo — criação de cobrança

Código completo mostrando criação de cobrança, polling de status e liberação de produto via webhook. Escolha sua linguagem:

# 1. Criar cobrança curl -X POST https://api.kaironpay.com/api/v1/client/charges \ -H "Authorization: Bearer ${API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "value": 9990, "comment": "Produto Premium - Pedido #1001", "correlationID": "pedido-1001-uuid", "customer": { "name": "João Silva", "taxID": "12345678900", "email": "joao@email.com" } }' # Resposta: use brCode para exibir copia-e-cola # Use qrCodeImage para exibir o QR Code # 2. Consultar status (polling) curl https://api.kaironpay.com/api/v1/client/charges/pedido-1001-uuid \ -H "Authorization: Bearer ${API_KEY}"
// kaironpay.js — Fluxo completo const API_KEY = process.env.KAIRONPAY_API_KEY; const BASE = 'https://api.kaironpay.com/api/v1/client'; async function kpFetch(path, options = {}) { const res = await fetch(`${BASE}${path}`, { ...options, headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', ...options.headers } }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(`KaironPay ${res.status}: ${err.message || res.statusText}`); } return res.json(); } // Criar cobrança async function createCharge(orderId, valueCents, customer) { return kpFetch('/charges', { method: 'POST', body: JSON.stringify({ value: valueCents, comment: `Pedido #${orderId}`, correlationID: `order-${orderId}`, customer }) }); } // Consultar cobrança async function getCharge(correlationID) { return kpFetch(`/charges/${correlationID}`); } // Uso const charge = await createCharge(1001, 9990, { name: 'João Silva', taxID: '12345678900', email: 'joao@email.com' }); console.log('PIX copia-e-cola:', charge.brCode); console.log('Link de pagamento:', charge.paymentLinkUrl); // Exibir charge.qrCodeImage em <img src={charge.qrCodeImage}>
# kaironpay.py — Fluxo completo import os, requests API_KEY = os.environ['KAIRONPAY_API_KEY'] BASE = 'https://api.kaironpay.com/api/v1/client' def kp_request(method, path, **kwargs): headers = { 'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json' } resp = requests.request(method, BASE + path, headers=headers, **kwargs) resp.raise_for_status() return resp.json() def create_charge(order_id, value_cents, customer=None): payload = { 'value': value_cents, 'comment': f'Pedido #{order_id}', 'correlationID': f'order-{order_id}' } if customer: payload['customer'] = customer return kp_request('POST', '/charges', json=payload) def get_charge(correlation_id): return kp_request('GET', f'/charges/{correlation_id}') # Uso charge = create_charge( order_id=1001, value_cents=9990, customer={'name': 'João Silva', 'taxID': '12345678900', 'email': 'joao@email.com'} ) print('PIX copia-e-cola:', charge['brCode']) print('Link de pagamento:', charge['paymentLinkUrl']) # charge['qrCodeImage'] é um base64 PNG
<?php // kaironpay.php — Fluxo completo $apiKey = getenv('KAIRONPAY_API_KEY'); $base = 'https://api.kaironpay.com/api/v1/client'; function kpRequest($method, $path, $body = null) { global $apiKey, $base; $ch = curl_init($base . $path); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => strtoupper($method), CURLOPT_HTTPHEADER => [ "Authorization: Bearer $apiKey", 'Content-Type: application/json' ], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, ]); if ($body) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); } $resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code >= 400) { $err = json_decode($resp, true); throw new RuntimeException("KaironPay $code: " . ($err['message'] ?? $resp)); } return json_decode($resp, true); } function createCharge($orderId, $valueCents, $customer = null) { $payload = [ 'value' => $valueCents, 'comment' => "Pedido #$orderId", 'correlationID' => "order-$orderId", ]; if ($customer) $payload['customer'] = $customer; return kpRequest('POST', '/charges', $payload); } // Uso $charge = createCharge(1001, 9990, [ 'name' => 'João Silva', 'taxID' => '12345678900', 'email' => 'joao@email.com' ]); echo "PIX: " . $charge['brCode'] . " "; echo "Link: " . $charge['paymentLinkUrl'] . " ";
// kaironpay.go — Fluxo completo package main import ( "bytes" "encoding/json" "fmt" "net/http" "os" ) const baseURL = "https://api.kaironpay.com/api/v1/client" type Customer struct { Name string `json:"name"` TaxID string `json:"taxID"` Email string `json:"email,omitempty"` } type ChargeRequest struct { Value int `json:"value"` Comment string `json:"comment"` CorrelationID string `json:"correlationID"` Customer *Customer `json:"customer,omitempty"` } type ChargeResponse struct { CorrelationID string `json:"correlationID"` Value int `json:"value"` Status string `json:"status"` BrCode string `json:"brCode"` QRCodeImage string `json:"qrCodeImage"` PaymentLinkURL string `json:"paymentLinkUrl"` ExpiresAt string `json:"expiresAt"` } func createCharge(req ChargeRequest) (*ChargeResponse, error) { apiKey := os.Getenv("KAIRONPAY_API_KEY") body, _ := json.Marshal(req) r, _ := http.NewRequest("POST", baseURL+"/charges", bytes.NewBuffer(body)) r.Header.Set("Authorization", "Bearer "+apiKey) r.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(r) if err != nil { return nil, err } defer resp.Body.Close() var charge ChargeResponse json.NewDecoder(resp.Body).Decode(&charge) return &charge, nil } func main() { charge, _ := createCharge(ChargeRequest{ Value: 9990, Comment: "Pedido #1001", CorrelationID: "order-1001", Customer: &Customer{ Name: "João Silva", TaxID: "12345678900", Email: "joao@email.com", }, }) fmt.Println("PIX:", charge.BrCode) fmt.Println("Link:", charge.PaymentLinkURL) }
# kaironpay.rb — Fluxo completo require 'net/http' require 'json' require 'uri' API_KEY = ENV['KAIRONPAY_API_KEY'] BASE = 'https://api.kaironpay.com/api/v1/client' def kp_request(method, path, body = nil) uri = URI(BASE + path) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req_class = { 'POST' => Net::HTTP::Post, 'GET' => Net::HTTP::Get, 'DELETE' => Net::HTTP::Delete }[method] req = req_class.new(uri) req['Authorization'] = "Bearer #{API_KEY}" req['Content-Type'] = 'application/json' req.body = body.to_json if body resp = http.request(req) raise "KaironPay #{resp.code}: #{resp.body}" unless resp.code.to_i < 400 JSON.parse(resp.body) end def create_charge(order_id, value_cents, customer = nil) payload = { value: value_cents, comment: "Pedido ##{order_id}", correlationID: "order-#{order_id}" } payload[:customer] = customer if customer kp_request('POST', '/charges', payload) end # Uso charge = create_charge(1001, 9990, { name: 'João Silva', taxID: '12345678900', email: 'joao@email.com' }) puts "PIX: #{charge['brCode']}" puts "Link: #{charge['paymentLinkUrl']}"

Handler de Webhook — liberar produto

Quando o pagamento é confirmado, o KaironPay envia TRANSACTION_COMPLETED. Valide o token, identifique o pedido pelo correlationID e libere o produto:

# O KaironPay faz POST para sua URL. Shape real (data.data aninhado, centavos): curl -X POST https://seusite.com/webhook/kaironpay \ -H "Content-Type: application/json" \ -H "X-Webhook-Token: seu_token_secreto" \ -d '{ "event": "PayInCompleted", "data": { "data": { "correlationID": "order-1001", "amount": 9990, "status": "paid", "payer": { "name": "João Silva", "taxID": "12345678900" } } } }'
// Express.js webhook handler const express = require('express'); const app = express(); // IMPORTANTE: usar express.raw para ter acesso ao body bruto app.use(express.json()); app.post('/webhook/kaironpay', (req, res) => { const token = req.headers['x-webhook-token']; const expectedToken = process.env.KAIRONPAY_WEBHOOK_TOKEN; // Valide o token — cada webhook tem seu token único do painel if (!token || token !== expectedToken) { return res.status(401).json({ error: 'Unauthorized' }); } // Responda 200 IMEDIATAMENTE — processe de forma assíncrona res.status(200).json({ received: true }); // Processamento assíncrono setImmediate(async () => { const { event, data } = req.body; const d = data.data || data; // PayIn* aninham em data.data switch (event) { case 'PayInCompleted': { const orderId = d.correlationID; // o correlationID que você enviou const amountPaidCents = d.amount; // em CENTAVOS await fulfillOrder(orderId, amountPaidCents); break; } case 'PayInRefunded': await handleRefund(d.correlationID); break; } }); }); async function fulfillOrder(orderId, amount) { // Idempotência: verifique se já processou const already = await db.orders.findFirst({ where: { correlationID: orderId, status: 'paid' } }); if (already) return; await db.orders.update({ where: { correlationID: orderId }, data: { status: 'paid' } }); await sendConfirmationEmail(orderId); console.log(`Pedido ${orderId} liberado — R$ ${(amount / 100).toFixed(2)}`); // amount em centavos } app.listen(3000);
# FastAPI webhook handler from fastapi import FastAPI, Request, HTTPException import asyncio, os app = FastAPI() WEBHOOK_TOKEN = os.environ['KAIRONPAY_WEBHOOK_TOKEN'] @app.post('/webhook/kaironpay') async def webhook(request: Request): token = request.headers.get('x-webhook-token') if token != WEBHOOK_TOKEN: raise HTTPException(status_code=401, detail='Unauthorized') payload = await request.json() event = payload.get('event') data = payload.get('data', {}) # Responda rápido e processe em background asyncio.create_task(process_event(event, data)) return {'received': True} async def process_event(event: str, data: dict): d = data.get('data', data) # PayIn* aninham em data.data if event == 'PayInCompleted': order_id = d['correlationID'] amount = d['amount'] # em CENTAVOS # Idempotência — verifique antes de processar order = await db.get_order(order_id) if order and order['status'] != 'paid': await db.update_order(order_id, status='paid') await send_confirmation_email(order_id) print(f'Pedido {order_id} liberado — R$ {amount/100:.2f}') elif event == 'PayInRefunded': await handle_refund(d['correlationID'])
<?php // webhook.php — handler puro PHP $webhookToken = getenv('KAIRONPAY_WEBHOOK_TOKEN'); // Validar token $receivedToken = $_SERVER['HTTP_X_WEBHOOK_TOKEN'] ?? ''; if (!hash_equals($webhookToken, $receivedToken)) { http_response_code(401); exit('Unauthorized'); } $payload = json_decode(file_get_contents('php://input'), true); $event = $payload['event'] ?? ''; $outer = $payload['data'] ?? []; $data = $outer['data'] ?? $outer; // PayIn* aninham em data.data // Responder 200 IMEDIATAMENTE http_response_code(200); header('Content-Type: application/json'); echo json_encode(['received' => true]); // Fechar conexão e processar em background if (function_exists('fastcgi_finish_request')) fastcgi_finish_request(); switch ($event) { case 'PayInCompleted': $orderId = $data['correlationID']; $amount = $data['amount']; // em CENTAVOS // Idempotência: marque o pedido como pago $stmt = $pdo->prepare("UPDATE orders SET status='paid' WHERE correlation_id=? AND status!='paid'"); if ($stmt->execute([$orderId]) && $stmt->rowCount() > 0) { sendConfirmationEmail($orderId); } break; case 'PayInRefunded': handleRefund($data['correlationID']); break; }
// webhook.go — net/http handler package main import ( "encoding/json" "fmt" "log" "net/http" "os" ) type WebhookPayload struct { Event string `json:"event"` Timestamp string `json:"timestamp"` Data map[string]interface{} `json:"data"` } func webhookHandler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Webhook-Token") if token != os.Getenv("KAIRONPAY_WEBHOOK_TOKEN") { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } var payload WebhookPayload json.NewDecoder(r.Body).Decode(&payload) // Responder 200 imediatamente w.WriteHeader(http.StatusOK) fmt.Fprintf(w, `{"received":true}`) // Processar em goroutine go func() { d := payload.Data // PayIn* aninham em data.data if inner, ok := payload.Data["data"].(map[string]interface{}); ok { d = inner } switch payload.Event { case "PayInCompleted": orderId := d["correlationID"].(string) log.Printf("Pedido %s pago! ", orderId) fulfillOrder(orderId) case "PayInRefunded": handleRefund(d["correlationID"].(string)) } }() } func main() { http.HandleFunc("/webhook/kaironpay", webhookHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
# Sinatra webhook handler require 'sinatra' require 'json' WEBHOOK_TOKEN = ENV['KAIRONPAY_WEBHOOK_TOKEN'] post '/webhook/kaironpay' do token = request.env['HTTP_X_WEBHOOK_TOKEN'] halt 401, 'Unauthorized' unless token == WEBHOOK_TOKEN payload = JSON.parse(request.body.read) event = payload['event'] outer = payload['data'] || {} data = outer['data'] || outer # PayIn* aninham em data.data # Responder imediatamente status 200 content_type :json body '{"received":true}' # Processar assíncronamente Thread.new do case event when 'PayInCompleted' order_id = data['correlationID'] puts "Pedido #{order_id} pago! (amount em centavos: #{data['amount']})" fulfill_order(order_id) when 'PayInRefunded' handle_refund(data['correlationID']) end end end

Autenticação

Todas as requisições devem incluir sua API Key no header Authorization usando o esquema Bearer.

Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxx

Como obter sua API Key

  1. Acesse o Painel KaironPayapp.kaironpay.com/dashboard
  2. Navegue até Configurações → API Key
  3. Clique em "Gerar nova chave" (ou copie a existente)
  4. Copie a chave no formato sk_*
  5. Salve como variável de ambiente: KAIRONPAY_API_KEY=sk_suachave

Exemplos de uso

curl -X POST https://api.kaironpay.com/api/v1/client/charges \ -H "Authorization: Bearer sk_sua_api_key" \ -H "Content-Type: application/json" \ -d '{"value": 1000, "comment": "Pedido #123"}'
const API_KEY = process.env.KAIRONPAY_API_KEY; const res = await fetch("https://api.kaironpay.com/api/v1/client/charges", { method: "POST", headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ value: 1000, comment: "Pedido #123" }) }); const data = await res.json(); console.log(data.brCode);
import requests, os API_KEY = os.environ['KAIRONPAY_API_KEY'] resp = requests.post( "https://api.kaironpay.com/api/v1/client/charges", headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}, json={"value": 1000, "comment": "Pedido #123"} ) resp.raise_for_status() print(resp.json()["brCode"])
$ch = curl_init("https://api.kaironpay.com/api/v1/client/charges"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv('KAIRONPAY_API_KEY'), "Content-Type: application/json" ], CURLOPT_POSTFIELDS => json_encode(['value' => 1000, 'comment' => 'Pedido #123']), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo $response['brCode'];
r, _ := http.NewRequest("POST", "https://api.kaironpay.com/api/v1/client/charges", strings.NewReader(`{"value":1000,"comment":"Pedido #123"}`)) r.Header.Set("Authorization", "Bearer "+os.Getenv("KAIRONPAY_API_KEY")) r.Header.Set("Content-Type", "application/json")
require 'net/http'; require 'json' uri = URI('https://api.kaironpay.com/api/v1/client/charges') req = Net::HTTP::Post.new(uri) req['Authorization'] = "Bearer #{ENV['KAIRONPAY_API_KEY']}" req['Content-Type'] = 'application/json' req.body = { value: 1000, comment: 'Pedido #123' }.to_json
Segurança: Nunca exponha sua API Key em código client-side (JavaScript no browser). Use sempre em backend (server-to-server). A API Key concede acesso total à sua conta.
Formato da chave: As chaves da API começam com o prefixo sk_. O header deve ser exatamente Authorization: Bearer <sua_chave>.
Rate limit: Atualmente não há um rate limit fixo aplicado às rotas /v1/client. Ainda assim, implemente retry com backoff exponencial para erros 5xx e use correlationID estável para segurança em retentativas.

Idempotência

Use correlationID para garantir que operações não sejam duplicadas em caso de falhas de rede ou retentativas.

Como funciona: Se você enviar o mesmo correlationID duas vezes, a API retorna a operação existente (status 200) ao invés de criar uma nova. Isso é seguro para retentativas.

Regras do correlationID

RegraDetalhe
FormatoString livre (alfanumérico, hífens e underscores). Recomendamos derivar do ID do pedido na sua aplicação. Não há tamanho mínimo obrigatório.
EscopoPor tipo de operação (cobranças e saques têm escopos separados)
Geração automáticaSe omitido, a API gera um correlationID — cobranças no formato KAI<timestamp><id> e boletos BOL<timestamp><id>
409 ConflictRetornado em saques/transferências se o mesmo correlationID for reutilizado. A resposta inclui existing_reference_code. Em cobranças, reenviar o mesmo correlationID retorna a cobrança existente (200, deduplicated: true)

Gerando correlationIDs únicos

# Use uuidgen ou similar para gerar IDs únicos CORRELATION_ID="order-$(uuidgen | tr '[:upper:]' '[:lower:]')" curl -X POST https://api.kaironpay.com/api/v1/client/charges \ -H "Authorization: Bearer ${API_KEY}" \ -H "Content-Type: application/json" \ -d "{"value": 1000, "correlationID": "$CORRELATION_ID"}"
const { randomUUID } = require('crypto'); // Baseie o correlationID no ID do pedido na sua aplicação // Assim retentativas do mesmo pedido são seguras function chargeCorrelationId(orderId) { // Combine ID do pedido com sufixo único (se precisar de múltiplas tentativas) return `charge-${orderId}-${randomUUID()}`; } // Melhor prática: use o mesmo correlationID para retentativas function stableCorrelationId(orderId) { // Sempre o mesmo ID para o mesmo pedido = retentativas seguras return `order-${orderId}`; // mínimo 26 chars }
import uuid # Baseie no ID do pedido na sua aplicação def stable_correlation_id(order_id: str) -> str: """Mesmo pedido = mesmo correlationID = retentativas seguras""" cid = f"order-{order_id}" if len(cid) < 26: cid = cid.ljust(26, '0') return cid # Para IDs únicos por tentativa: def unique_correlation_id(prefix="charge") -> str: return f"{prefix}-{uuid.uuid4()}"
function stableCorrelationId(string $orderId): string { $id = "order-$orderId"; // Garante tamanho mínimo de 26 chars return str_pad($id, 26, '0'); } function uniqueCorrelationId(string $prefix = 'charge'): string { return "$prefix-" . str_replace('-', '', (string) Str::uuid()); }
import "github.com/google/uuid" func stableCorrelationID(orderID string) string { id := "order-" + orderID for len(id) < 26 { id += "0" } return id } func uniqueCorrelationID(prefix string) string { return prefix + "-" + uuid.New().String() }
require 'securerandom' def stable_correlation_id(order_id) id = "order-#{order_id}" id.ljust(26, '0') end def unique_correlation_id(prefix = 'charge') "#{prefix}-#{SecureRandom.uuid}" end
Melhor prática: Use um correlationID estável e derivado do ID do pedido na sua aplicação. Assim, se a requisição falhar e você retentá-la, o servidor reconhece como idempotente e não cria uma cobrança duplicada.

Erros & Retry

Como identificar erros, quando retentativas são seguras e como implementar backoff exponencial.

Quando retentativas são seguras

CódigoRetentativa segura?Estratégia
2xxN/ASucesso — não retente
400NãoCorrija os dados antes de retentativas
401NãoVerifique/renove a API Key
403NãoProblema de permissão — contate suporte
404NãoRecurso não existe
409VerificarcorrelationID duplicado — pode buscar o recurso existente
500SimBackoff exponencial com jitter
TimeoutSim**Use correlationID estável para idempotência

Implementação de retry com backoff exponencial

# cURL tem retry nativo com --retry curl --retry 3 \ --retry-delay 2 \ --retry-max-time 30 \ --retry-on-http-error "429,500,502,503" \ -X POST https://api.kaironpay.com/api/v1/client/charges \ -H "Authorization: Bearer ${API_KEY}" \ -H "Content-Type: application/json" \ -d '{"value": 1000}'
// Retry com backoff exponencial e jitter async function kpFetchWithRetry(path, options = {}, maxRetries = 3) { const RETRYABLE = [429, 500, 502, 503, 504]; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { const res = await fetch(`https://api.kaironpay.com/api/v1/client${path}`, { ...options, headers: { 'Authorization': `Bearer ${process.env.KAIRONPAY_API_KEY}`, 'Content-Type': 'application/json', ...options.headers } }); if (!RETRYABLE.includes(res.status)) { if (!res.ok) throw Object.assign(new Error(), await res.json()); return res.json(); } if (attempt === maxRetries) throw new Error(`Max retries (${maxRetries}) excedidas`); // Backoff: 1s, 2s, 4s + jitter aleatório const retryAfter = res.headers.get('Retry-After'); const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.min(1000 * Math.pow(2, attempt) + Math.random() * 500, 30000); console.warn(`Tentativa ${attempt + 1} falhou (HTTP ${res.status}). Aguardando ${delay}ms...`); await new Promise(r => setTimeout(r, delay)); } catch (err) { if (attempt === maxRetries || !err.message.includes('fetch')) throw err; const delay = 1000 * Math.pow(2, attempt); await new Promise(r => setTimeout(r, delay)); } } }
import time, random, requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry # Opção 1: requests.Session com Retry automático def make_session() -> requests.Session: session = requests.Session() retry = Retry( total=3, backoff_factor=1, # 1s, 2s, 4s status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET", "POST", "DELETE"], respect_retry_after_header=True ) session.mount("https://", HTTPAdapter(max_retries=retry)) session.headers["Authorization"] = f"Bearer {os.environ['KAIRONPAY_API_KEY']}" return session # Opção 2: retry manual com jitter def kp_request_with_retry(method, path, max_retries=3, **kwargs): RETRYABLE = {429, 500, 502, 503, 504} session = make_session() for attempt in range(max_retries + 1): resp = session.request(method, f"https://api.kaironpay.com/api/v1/client{path}", **kwargs) if resp.status_code not in RETRYABLE: resp.raise_for_status() return resp.json() if attempt == max_retries: resp.raise_for_status() delay = (2 ** attempt) + random.uniform(0, 0.5) print(f"Tentativa {attempt+1} falhou (HTTP {resp.status_code}). Aguardando {delay:.1f}s...") time.sleep(delay)
function kpRequestWithRetry(string $method, string $path, array $body = [], int $maxRetries = 3): array { $retryable = [429, 500, 502, 503, 504]; for ($attempt = 0; $attempt <= $maxRetries; $attempt++) { $ch = curl_init(getenv('KAIRONPAY_BASE') . $path); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => strtoupper($method), CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv('KAIRONPAY_API_KEY'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => $body ? json_encode($body) : null, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HEADER => true, ]); $raw = curl_exec($ch); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $respBody = substr($raw, $headerSize); curl_close($ch); if (!in_array($code, $retryable)) { if ($code >= 400) throw new RuntimeException("KaironPay $code: $respBody"); return json_decode($respBody, true); } if ($attempt === $maxRetries) throw new RuntimeException("Max retries exceeded"); $delay = (int) pow(2, $attempt) * 1000000 + rand(0, 500000); usleep($delay); } }
import ("math"; "math/rand"; "time") func kpRequestWithRetry(method, path string, body []byte, maxRetries int) ([]byte, error) { retryable := map[int]bool{429: true, 500: true, 502: true, 503: true, 504: true} for attempt := 0; attempt <= maxRetries; attempt++ { req, _ := http.NewRequest(method, baseURL+path, bytes.NewBuffer(body)) req.Header.Set("Authorization", "Bearer "+os.Getenv("KAIRONPAY_API_KEY")) req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { if attempt == maxRetries { return nil, err } time.Sleep(time.Duration(math.Pow(2, float64(attempt))) * time.Second) continue } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if !retryable[resp.StatusCode] { if resp.StatusCode >= 400 { return nil, fmt.Errorf("KaironPay %d: %s", resp.StatusCode, respBody) } return respBody, nil } if attempt == maxRetries { return nil, fmt.Errorf("max retries exceeded") } jitter := time.Duration(rand.Intn(500)) * time.Millisecond time.Sleep(time.Duration(math.Pow(2, float64(attempt)))*time.Second + jitter) } return nil, nil }
require 'net/http' def kp_request_with_retry(method, path, body = nil, max_retries: 3) RETRYABLE = [429, 500, 502, 503, 504].freeze (0..max_retries).each do |attempt| result = kp_request(method, path, body) return result rescue RuntimeError => e code = e.message.match(/(d{3})/)[1].to_i rescue 0 raise e unless RETRYABLE.include?(code) && attempt < max_retries delay = (2**attempt) + rand(0.0..0.5) puts "Tentativa #{attempt+1} falhou (HTTP #{code}). Aguardando #{delay.round(1)}s..." sleep(delay) end end

Cobranças PIX

Crie, liste, consulte, cancele e estorne cobranças PIX com QR Code dinâmico.

POST/api/v1/client/charges

Cria uma cobrança PIX e retorna o QR Code e o código copia-e-cola (brCode). A cobrança fica com status ACTIVE até ser paga ou expirar. Idempotente via correlationID.

Parâmetros do body (JSON)

ParâmetroTipoDescrição
valueintegerobrigatórioValor em centavos. Ex: 1500 = R$ 15,00
commentstringDescrição da cobrança exibida ao pagador
correlationIDstringID único na sua aplicação. Gerado automaticamente (KAI…) se omitido.
customerobjectobrigatório*Dados do pagador (ver abaixo). *Obrigatório para a maioria dos provedores
customer.namestringNome completo do pagador
customer.taxIDstringobrigatório*CPF (11 dígitos) ou CNPJ (14 dígitos), apenas números. *Obrigatório junto com customer
customer.emailstringEmail válido do pagador
customer.phonestringTelefone com DDD. Ex: +5511999887766
expiresDatestringData de expiração ISO 8601. Ex: 2026-12-31T23:59:59Z
curl -X POST https://api.kaironpay.com/api/v1/client/charges \ -H "Authorization: Bearer sk_sua_api_key" \ -H "Content-Type: application/json" \ -d '{ "value": 1500, "comment": "Pedido #456 - Produto Premium", "correlationID": "order-456-uuid-aqui-1234", "expiresDate": "2026-06-08T23:59:59Z", "customer": { "name": "João Silva", "taxID": "12345678900", "email": "joao@email.com", "phone": "+5511999887766" } }'
const charge = await kpFetch('/charges', { method: 'POST', body: JSON.stringify({ value: 1500, comment: 'Pedido #456 - Produto Premium', correlationID: 'order-456-uuid-aqui-1234', expiresDate: '2026-06-08T23:59:59Z', customer: { name: 'João Silva', taxID: '12345678900', email: 'joao@email.com', phone: '+5511999887766' } }) }); console.log(charge.brCode); // Copia-e-cola (topo da resposta) console.log(charge.qrCodeImage); // <img src={charge.qrCodeImage}> // paymentLinkUrl vem em GET /charges/:correlationID → res.data.paymentLinkUrl
charge = kp_request('POST', '/charges', json={ 'value': 1500, 'comment': 'Pedido #456 - Produto Premium', 'correlationID': 'order-456-uuid-aqui-1234', 'expiresDate': '2026-06-08T23:59:59Z', 'customer': { 'name': 'João Silva', 'taxID': '12345678900', 'email': 'joao@email.com', 'phone': '+5511999887766' } }) print(charge['brCode']) # copia-e-cola # paymentLinkUrl: GET /charges/:correlationID → res['data']['paymentLinkUrl']
$charge = kpRequest('POST', '/charges', [ 'value' => 1500, 'comment' => 'Pedido #456 - Produto Premium', 'correlationID' => 'order-456-uuid-aqui-1234', 'expiresDate' => '2026-06-08T23:59:59Z', 'customer' => [ 'name' => 'João Silva', 'taxID' => '12345678900', 'email' => 'joao@email.com', 'phone' => '+5511999887766' ] ]); echo $charge['brCode']; // copia-e-cola // paymentLinkUrl: GET /charges/:correlationID → $res['data']['paymentLinkUrl']
charge, err := createCharge(ChargeRequest{ Value: 1500, Comment: "Pedido #456 - Produto Premium", CorrelationID: "order-456-uuid-aqui-1234", Customer: &Customer{ Name: "João Silva", TaxID: "12345678900", Email: "joao@email.com", }, }) fmt.Println(charge.BrCode) // paymentLinkUrl vem em GET /charges/:correlationID (res.Data.PaymentLinkURL)
charge = kp_request('POST', '/charges', { value: 1500, comment: 'Pedido #456 - Produto Premium', correlationID: 'order-456-uuid-aqui-1234', expiresDate: '2026-06-08T23:59:59Z', customer: { name: 'João Silva', taxID: '12345678900', email: 'joao@email.com' } }) puts charge['brCode']
Resposta de sucesso (201 Created)
{ "success": true, "correlationID": "KAI20260607100000abcd1234", "brCode": "00020101021226940014br.gov.bcb.pix2572...", "qrCodeImage": "data:image/png;base64,iVBORw0KGgo...", "usedProvider": "OASYFY", "fallbackUsed": false, "data": { /* objeto do provedor — campos variam conforme o provedor */ } }
Envelope: a resposta é embrulhada em { success, ... }. Os campos confiáveis no topo são correlationID, brCode e qrCodeImage. O objeto data reflete a resposta bruta do provedor (formato varia). Para obter a cobrança normalizada (com value, status, paymentLinkUrl, expiresAt) use GET /charges/:correlationID.
Idempotência: reenviar o mesmo correlationID retorna 200 com deduplicated: true e um data normalizado { correlationID, value, status, brCode, qrCodeImage, paymentLinkUrl, expiresAt, createdAt }.

Campos da resposta (topo)

CampoTipoDescrição
successbooleanSempre true em caso de sucesso
correlationIDstringID único da cobrança (o seu, ou gerado KAI…)
brCodestringCódigo PIX copia-e-cola (EMV)
qrCodeImagestringQR Code em base64 PNG — use direto em <img src="...">
usedProviderstringProvedor PIX efetivamente utilizado
fallbackUsedbooleantrue se um provedor de fallback foi usado
dataobjectResposta bruta do provedor (na resposta idempotente 200, é o objeto normalizado)
GET/api/v1/client/charges

Lista todas as cobranças da conta com filtros e paginação. Resultados ordenados por data de criação decrescente.

Query parameters

ParâmetroTipoDefaultDescrição
skipinteger0Offset para paginação
limitinteger100Máximo de resultados (max: 500)
statusstringACTIVE | COMPLETED | EXPIRED | REFUNDED | CANCELLED
start_datestringData inicial ISO 8601
end_datestringData final ISO 8601
curl "https://api.kaironpay.com/api/v1/client/charges?status=COMPLETED&limit=50&start_date=2026-06-01T00:00:00Z" \ -H "Authorization: Bearer sk_sua_api_key"
const params = new URLSearchParams({ status: 'COMPLETED', limit: '50', start_date: '2026-06-01T00:00:00Z' }); const data = await kpFetch(`/charges?${params}`); console.log(`Total: ${data.pageInfo.totalCount}, retornados: ${data.data.length}`);
data = kp_request('GET', '/charges', params={ 'status': 'COMPLETED', 'limit': 50, 'start_date': '2026-06-01T00:00:00Z' }) for charge in data['data']: print(charge['correlationID'], charge['value'], charge['status'])
$data = kpRequest('GET', '/charges?status=COMPLETED&limit=50&start_date=2026-06-01T00:00:00Z'); foreach ($data['data'] as $charge) { echo $charge['correlationID'] . ' - R$ ' . number_format($charge['value'] / 100, 2) . " "; }
respBody, _ := kpRequestWithRetry("GET", "/charges?status=COMPLETED&limit=50", nil, 3) var result map[string]interface{} json.Unmarshal(respBody, &result)
data = kp_request('GET', '/charges?status=COMPLETED&limit=50&start_date=2026-06-01T00:00:00Z') data['data'].each { |c| puts "#{c['correlationID']} — R$ #{c['value'] / 100.0}" }
Resposta (200)
{ "success": true, "data": [ { "correlationID": "KAI20260607100000abcd1234", "value": 1500, "status": "COMPLETED", "provider": "OASYFY", "brCode": "00020101...", "qrCodeImage": "data:image/png;base64,...", "paymentLinkUrl": "https://app.kaironpay.com/pay/abc123", "endToEndId": "E123456782026060710020000001", "comment": "Pedido #456", "customer": { "name": "João Silva", "email": "joao@email.com", "phone": null, "taxID": "12345678900" }, "createdAt": "2026-06-07T10:00:00Z", "paidAt": "2026-06-07T10:02:00Z", "expiresAt": "2026-06-08T10:00:00Z", "feeAmount": 45, "netAmount": 1455, "isRefunded": false, "refundedAt": null, "refundedAmount": 0 } ], "pageInfo": { "skip": 0, "limit": 50, "totalCount": 142, "hasPreviousPage": false, "hasNextPage": true }, "filters": { "status": "COMPLETED", "start_date": "2026-06-01T00:00:00Z", "end_date": null } }
Valores (value, feeAmount, netAmount, refundedAmount) em centavos. Itens em data[], paginação em pageInfo (totalCount, hasNextPage…).
GET/api/v1/client/charges/:correlationID

Consulta uma cobrança específica pelo correlationID. Retorna todos os campos incluindo brCode, qrCodeImage e dados do pagador.

curl https://api.kaironpay.com/api/v1/client/charges/order-456-uuid-aqui-1234 \ -H "Authorization: Bearer sk_sua_api_key"
const res = await kpFetch('/charges/order-456-uuid-aqui-1234'); const charge = res.data; if (charge.status === 'COMPLETED') { console.log('Pago!', charge.paidAt); }
res = kp_request('GET', '/charges/order-456-uuid-aqui-1234') charge = res['data'] if charge['status'] == 'COMPLETED': print('Pago em', charge['paidAt'])
$res = kpRequest('GET', '/charges/order-456-uuid-aqui-1234'); $charge = $res['data']; if ($charge['status'] === 'COMPLETED') { echo 'Pago em: ' . $charge['paidAt']; }
body, _ := kpRequestWithRetry("GET", "/charges/order-456-uuid-aqui-1234", nil, 3) var res struct{ Data ChargeResponse `json:"data"` } json.Unmarshal(body, &res) fmt.Println(res.Data.Status)
res = kp_request('GET', '/charges/order-456-uuid-aqui-1234') puts "Status: #{res['data']['status']}"
Resposta (200)
{ "success": true, "data": { "correlationID": "KAI20260607100000abcd1234", "value": 1500, "status": "COMPLETED", "provider": "OASYFY", "brCode": "00020101...", "qrCodeImage": "data:image/png;base64,...", "paymentLinkUrl": "https://app.kaironpay.com/pay/abc123", "endToEndId": "E123456782026060710020000001", "customer": { "name": "João Silva", "email": "joao@email.com", "phone": null, "taxID": "12345678900" }, "comment": "Pedido #456", "createdAt": "2026-06-07T10:00:00Z", "paidAt": "2026-06-07T10:02:00Z", "expiresAt": "2026-06-08T10:00:00Z", "feeAmount": 45, "netAmount": 1455, "isRefunded": false, "refundedAt": null, "refundedAmount": 0 } }
Também disponível: GET /api/v1/client/charges/e2e/:endToEndId — mesma resposta, buscando pelo endToEndId.
DELETE/api/v1/client/charges/:correlationID

Cancela/exclui uma cobrança. Disponível apenas para o provedor Woovi — para os demais provedores retorna 400 Not supported.

curl -X DELETE https://api.kaironpay.com/api/v1/client/charges/order-456-uuid-aqui-1234 \ -H "Authorization: Bearer sk_sua_api_key"
await kpFetch('/charges/order-456-uuid-aqui-1234', { method: 'DELETE' });
kp_request('DELETE', '/charges/order-456-uuid-aqui-1234')
kpRequest('DELETE', '/charges/order-456-uuid-aqui-1234');
kpRequestWithRetry("DELETE", "/charges/order-456-uuid-aqui-1234", nil, 3)
kp_request('DELETE', '/charges/order-456-uuid-aqui-1234')
Resposta (200)
{ "success": true, "message": "Charge deleted successfully" }
Reembolsos: o estorno de uma cobrança paga é feito pelo endpoint POST /api/v1/client/refunds — veja a seção Reembolsos.
Status possíveis: ACTIVE (aguardando pgto), COMPLETED (pago), EXPIRED (expirado), REFUNDED (reembolsado), CANCELLED (cancelado).

Saldo

Consulte o saldo disponível, pendente e reservado da sua conta.

GET/api/v1/client/balance

Retorna o saldo detalhado da conta. Cada valor vem em centavos (campos numéricos) e também já formatado em BRL (campos *Formatted).

curl https://api.kaironpay.com/api/v1/client/balance \ -H "Authorization: Bearer sk_sua_api_key"
const res = await kpFetch('/balance'); const b = res.data; console.log(`Disponível: ${b.availableBalanceFormatted}`); // "R$ 2.500,00" console.log(`Em centavos: ${b.availableBalance}`); // 250000
res = kp_request('GET', '/balance') b = res['data'] print(f"Disponível: {b['availableBalanceFormatted']}") # "R$ 2.500,00" print(f"Em centavos: {b['availableBalance']}") # 250000
$res = kpRequest('GET', '/balance'); $b = $res['data']; echo 'Disponível: ' . $b['availableBalanceFormatted']; // "R$ 2.500,00"
body, _ := kpRequestWithRetry("GET", "/balance", nil, 3) var res struct{ Data map[string]interface{} `json:"data"` } json.Unmarshal(body, &res) fmt.Printf("Disponível: %v ", res.Data["availableBalanceFormatted"])
res = kp_request('GET', '/balance') b = res['data'] puts "Disponível: #{b['availableBalanceFormatted']}"
Resposta (200)
{ "success": true, "data": { "availableBalance": 250000, "availableBalanceFormatted": "R$ 2.500,00", "manualBalanceAdjustment": 0, "manualBalanceAdjustmentFormatted": "R$ 0,00", "totalReceived": 500000, "totalReceivedFormatted": "R$ 5.000,00", "totalFees": 15000, "totalFeesFormatted": "R$ 150,00", "totalPayouts": 235000, "totalPayoutsFormatted": "R$ 2.350,00", "totalPendingPayouts": 0, "totalPendingPayoutsFormatted": "R$ 0,00", "totalBlocked": 0, "totalBlockedFormatted": "R$ 0,00", "heldBoletoBalance": 0, "heldBoletoBalanceFormatted": "R$ 0,00", "withdrawalsBlocked": false, "withdrawalsBlockedReason": null } }

Campos da resposta (dentro de data)

CampoDescrição
availableBalanceSaldo disponível para saque (centavos). availableBalanceFormatted traz a versão em BRL.
manualBalanceAdjustmentAjuste manual aplicado ao saldo (centavos)
totalReceivedTotal bruto recebido em cobranças (centavos)
totalFeesTotal de taxas cobradas (centavos)
totalPayoutsTotal de saques concluídos (centavos)
totalPendingPayoutsSaques pendentes/em processamento (centavos)
totalBlockedValor bloqueado por disputas/MED (centavos)
heldBoletoBalanceSaldo de boletos ainda retido (centavos)
withdrawalsBlockedboolean — se os saques estão bloqueados na conta
withdrawalsBlockedReasonMotivo do bloqueio (ou null)
Cada campo numérico tem um par <campo>Formatted com a string em BRL (ex: "R$ 2.500,00"). Não existem os campos pending, reserved, total ou currency.

Transações

Consulte o extrato completo de movimentações da conta — PIX IN, PIX OUT, estornos e ajustes.

GET/api/v1/client/transactions

Lista todas as movimentações da conta com filtros e paginação. Resultados ordenados por data decrescente.

Query parameters

ParâmetroTipoDefaultDescrição
typestringCREDIT (entradas/PIX IN) | DEBIT (saídas/PIX OUT)
statusstringFiltra pelo status (ex: COMPLETED, PENDING)
skipinteger0Offset de paginação
limitinteger100Máximo de resultados (max: 500)
start_datestringData inicial ISO 8601
end_datestringData final ISO 8601
curl "https://api.kaironpay.com/api/v1/client/transactions?type=CREDIT&limit=50" \ -H "Authorization: Bearer sk_sua_api_key"
const params = new URLSearchParams({ type: 'CREDIT', limit: '50' }); const res = await kpFetch(`/transactions?${params}`); res.data.forEach(tx => { console.log(`${tx.type} | R$ ${(tx.value/100).toFixed(2)} | ${tx.description}`); });
res = kp_request('GET', '/transactions', params={'type': 'CREDIT', 'limit': 50}) for tx in res['data']: print(f"{tx['type']} | R$ {tx['value']/100:.2f} | {tx['description']}")
$res = kpRequest('GET', '/transactions?type=CREDIT&limit=50'); foreach ($res['data'] as $tx) { echo $tx['type'] . ' | R$ ' . number_format($tx['value'] / 100, 2) . " "; }
body, _ := kpRequestWithRetry("GET", "/transactions?type=CREDIT&limit=50", nil, 3)
res = kp_request('GET', '/transactions?type=CREDIT&limit=50') res['data'].each { |tx| puts "#{tx['type']} | R$ #{tx['value']/100.0}" }
Resposta (200)
{ "success": true, "data": [ { "id": "KAI20260607100000abcd1234", "type": "CREDIT", "description": "Pedido #456", "value": 1500, "netValue": 1455, "fee": 45, "status": "COMPLETED", "referenceId": null, "pixKey": null, "recipientName": null, "createdAt": "2026-06-07T10:02:00Z", "completedAt": "2026-06-07T10:02:05Z" } ], "pagination": { "total": 89, "skip": 0, "limit": 50, "hasMore": true } }
Formato: itens em data[] (unifica cobranças e saques), paginação em pagination. Campos monetários (value, netValue, fee) em centavos. Linhas DEBIT (saques) trazem também pixKey e recipientName.

Saques e Transferências

Envie PIX para qualquer chave — saques automáticos ou com aprovação manual.

Modos de aprovação: auto (padrão) — processado imediatamente se saldo disponível. manual — fica pendente até aprovação de um admin no painel.
POST/api/v1/client/payouts

Cria um saque PIX. O valor é debitado do saldo disponível imediatamente. Idempotente via correlationID.

Parâmetros do body (JSON)

ParâmetroTipoDescrição
valueintegerobrigatórioValor em centavos. Mínimo: 100 (R$ 1,00)
pixKeystringobrigatórioChave PIX de destino
pixKeyTypestringobrigatóriocpf | cnpj | email | phone | evp
descriptionstringDescrição do saque (aparece no extrato)
correlationIDstringID único para evitar duplicatas (idempotência)
recipientDocumentstringCPF/CNPJ do destinatário (também aceito como payeeTaxId ou destinationDocument)

Tipos de chave PIX (pixKeyType)

pixKeyTypeFormatoExemplo
cpf11 dígitos numéricos12345678900
cnpj14 dígitos numéricos12345678000190
emailEmail válidojoao@email.com
phone+55 + DDD + número+5511999887766
evpChave aleatória (UUID)a1b2c3d4-e5f6-7890-abcd-ef1234567890
curl -X POST https://api.kaironpay.com/api/v1/client/payouts \ -H "Authorization: Bearer sk_sua_api_key" \ -H "Content-Type: application/json" \ -d '{ "value": 5000, "pixKey": "joao@email.com", "pixKeyType": "email", "description": "Pagamento comissão vendedor", "correlationID": "payout-comissao-vendedor-001" }'
const payout = await kpFetch('/payouts', { method: 'POST', body: JSON.stringify({ value: 5000, pixKey: 'joao@email.com', pixKeyType: 'email', description: 'Pagamento comissão vendedor', correlationID: 'payout-comissao-vendedor-001' }) }); console.log('Saque criado:', payout.data.reference_code, payout.data.status);
payout = kp_request('POST', '/payouts', json={ 'value': 5000, 'pixKey': 'joao@email.com', 'pixKeyType': 'email', 'description': 'Pagamento comissão vendedor', 'correlationID': 'payout-comissao-vendedor-001' }) print(f"Saque: {payout['data']['reference_code']} | Status: {payout['data']['status']}")
$payout = kpRequest('POST', '/payouts', [ 'value' => 5000, 'pixKey' => 'joao@email.com', 'pixKeyType' => 'email', 'description' => 'Pagamento comissão vendedor', 'correlationID' => 'payout-comissao-vendedor-001' ]); echo 'Saque: ' . $payout['data']['reference_code'] . ' | Status: ' . $payout['data']['status'];
body, _ := json.Marshal(map[string]interface{}{ "value": 5000, "pixKey": "joao@email.com", "pixKeyType": "email", "description": "Pagamento comissão", "correlationID": "payout-comissao-001", }) resp, _ := kpRequestWithRetry("POST", "/payouts", body, 3)
payout = kp_request('POST', '/payouts', { value: 5000, pixKey: 'joao@email.com', pixKeyType: 'email', description: 'Pagamento comissão', correlationID: 'payout-comissao-001' }) puts "Saque: #{payout['data']['reference_code']} | #{payout['data']['status']}"
Resposta (201 Created)
{ "success": true, "data": { "reference_code": "payout_x7k9m2a1b3c4d5e6", "amount": 50.00, "net_amount": 50.00, "fee": 1.50, "status": "processing", "source": "API", "message": "Withdrawal is being processed" } }
Unidades (atenção): aqui amount, net_amount e fee vêm em reais (decimal). A taxa é cobrada além do valor (o total debitado do saldo é value + fee), por isso net_amount é igual a amount. Status inicial: processing (modo auto) ou pending_approval (modo manual).
Erros comuns: 400 saldo insuficiente (error: "Insufficient balance"), 400 chave PIX inválida (error: "Invalid PIX key"), 400 limite de saque excedido, 403 saques bloqueados na conta, 409 correlationID duplicado (retorna existing_reference_code).
GET/api/v1/client/payouts

Lista todos os saques da conta com filtros e paginação.

ParâmetroTipoDefaultDescrição
skipinteger0Offset para paginação
limitinteger100Máximo de resultados (max: 500)
statusstringCOMPLETED | PENDING | FAILED | REJECTED | processing
start_datestringData inicial ISO 8601
end_datestringData final ISO 8601
curl "https://api.kaironpay.com/api/v1/client/payouts?status=COMPLETED&limit=50" \ -H "Authorization: Bearer sk_sua_api_key"
const res = await kpFetch('/payouts?status=COMPLETED&limit=50'); console.log(`${res.data.length} saques nesta página (hasMore: ${res.pagination.hasMore})`);
data = kp_request('GET', '/payouts', params={'status': 'COMPLETED', 'limit': 50})
$data = kpRequest('GET', '/payouts?status=COMPLETED&limit=50');
kpRequestWithRetry("GET", "/payouts?status=COMPLETED&limit=50", nil, 3)
data = kp_request('GET', '/payouts?status=COMPLETED&limit=50')
Resposta (200)
{ "success": true, "data": [ { "referenceCode": "payout_x7k9m2a1b3c4d5e6", "value": 5000, "status": "COMPLETED", "pixKey": "joao@email.com", "pixKeyType": "email", "recipientName": "João Silva", "recipientDocument": "***.***.***-**", "description": "Pagamento comissão vendedor", "createdAt": "2026-06-07T10:00:00Z", "approvedAt": "2026-06-07T10:00:02Z", "completedAt": "2026-06-07T10:00:05Z", "errorMessage": null } ], "pagination": { "skip": 0, "limit": 50, "hasMore": false } }
Atenção à unidade: na lista de saques o campo value vem em centavos (ex: 5000 = R$ 50,00) — diferente do POST /payouts e do GET /payouts/:reference_code, que retornam amount em reais. O recipientDocument vem mascarado.
GET/api/v1/client/payouts/:reference_code

Consulta o status de um saque específico pelo reference_code.

Aceita tanto o reference_code quanto o correlationID que você enviou no saque.

Resposta (200)
{ "success": true, "data": { "reference_code": "payout_x7k9m2a1b3c4d5e6", "amount": 50.00, "net_amount": 50.00, "fee": 1.50, "status": "completed", "pix_key": "joao@email.com", "pix_key_type": "email", "created_at": "2026-06-07T10:00:00Z", "completed_at": "2026-06-07T10:00:05Z", "provider": "RAPDYN", "error_message": null, "source": "local_database" } }
Unidades: amount, net_amount e fee em reais. status em minúsculas.
Fluxo de status: pending_approval (modo manual) → processingcompleted | failed

Reembolsos

Estorne total ou parcialmente cobranças já pagas. O valor é devolvido ao pagador via PIX em instantes.

Disponibilidade: O reembolso é feito por POST /api/v1/client/refunds. Atualmente suportado apenas para cobranças criadas nos provedores Woovi e Venit — para os demais retorna 400 Not supported.
POST/api/v1/client/refunds

Cria um reembolso para uma cobrança já paga (COMPLETED). Suporta reembolso total ou parcial.

Parâmetros do body (JSON)

ParâmetroTipoDescrição
correlationIDstringobrigatóriocorrelationID da cobrança original a ser reembolsada
valueintegerobrigatórioValor do reembolso em centavos. Para reembolso total, use o valor original da cobrança.
commentstringMotivo do reembolso
# Reembolso parcial de R$ 5,00 curl -X POST https://api.kaironpay.com/api/v1/client/refunds \ -H "Authorization: Bearer sk_sua_api_key" \ -H "Content-Type: application/json" \ -d '{ "correlationID": "order-456-uuid-aqui-1234", "value": 500, "comment": "Produto danificado na entrega" }'
const refund = await kpFetch('/refunds', { method: 'POST', body: JSON.stringify({ correlationID: 'order-456-uuid-aqui-1234', value: 500, comment: 'Produto danificado na entrega' }) }); console.log('Reembolso:', refund.data.id, refund.data.status);
refund = kp_request('POST', '/refunds', json={ 'correlationID': 'order-456-uuid-aqui-1234', 'value': 500, 'comment': 'Produto danificado' })
$refund = kpRequest('POST', '/refunds', [ 'correlationID' => 'order-456-uuid-aqui-1234', 'value' => 500, 'comment' => 'Produto danificado' ]);
body, _ := json.Marshal(map[string]interface{}{ "correlationID": "order-456-uuid-aqui-1234", "value": 500, "comment": "Produto danificado", }) kpRequestWithRetry("POST", "/refunds", body, 3)
refund = kp_request('POST', '/refunds', { correlationID: 'order-456-uuid-aqui-1234', value: 500, comment: 'Produto danificado' })
Resposta (201 Created)
{ "success": true, "data": { "id": "refund_xyz789", "status": "REFUNDED", "value": 500, "correlationID": "order-456-uuid-aqui-1234", "createdAt": "2026-06-07T12:00:00.000Z" } }
value em centavos. O objeto data é embrulhado em { success, data }. Para cobranças Woovi, data reflete a resposta do provedor.

Regras de reembolso

RegraDescrição
Status obrigatórioApenas cobranças com status COMPLETED podem ser reembolsadas
Valor máximoO valor do reembolso não pode exceder o valor original da cobrança
Reembolso parcialSuportado — envie um valor menor que o original
Limite por cobrançaApenas 1 reembolso por cobrança
SaldoO valor é debitado do saldo disponível da sua conta
WebhookEvento TRANSACTION_REFUNDED (PayInRefunded) é enviado após processamento

Webhooks

Receba notificações em tempo real quando eventos ocorrem na sua conta. O KaironPay envia um POST HTTP para a URL cadastrada.

Como funciona

  • Cadastre sua URL

    Acesse o painel → Configurações → Webhooks → Nova URL. Informe sua URL HTTPS acessível publicamente.

  • Copie o token

    Cada webhook tem um token único gerado automaticamente. Salve como variável de ambiente (KAIRONPAY_WEBHOOK_TOKEN). Este token é enviado no header X-Webhook-Token de cada requisição.

  • Escolha os eventos

    Selecione quais eventos deseja receber: cobranças, saques, reembolsos, etc.

  • Implemente o handler

    Valide o token, responda HTTP 2xx em até 5 segundos e processe de forma assíncrona.

Headers enviados pelo KaironPay

HeaderDescriçãoExemplo
Content-TypeTipo do conteúdoapplication/json
X-Webhook-TokenToken único do webhook (do painel). É também a chave usada no HMAC.seu_token_secreto
X-KaironPay-EventNome interno do evento (snake_case) — veja a coluna X-KaironPay-Event em "Todos os Eventos"TRANSACTION_COMPLETED
X-KaironPay-SignatureAssinatura HMAC-SHA256 do payload (hex)3a5f...e91
X-KaironPay-TimestampTimestamp Unix (segundos) usado na assinatura1749297600
X-KaironPay-AlgorithmAlgoritmo da assinaturaHMAC-SHA256
User-AgentIdentifica o entregadorKaironPay-Webhook/1.0
Nome do evento no header vs. no body: o header X-KaironPay-Event usa o nome interno em SNAKE_CASE (ex: TRANSACTION_COMPLETED, PAYOUT_COMPLETED), enquanto o campo event do corpo usa o nome em PascalCase (ex: PayInCompleted, PayOutCompleted). São diferentes — trate cada um pelo seu valor.
Compatibilidade: por retrocompatibilidade, os mesmos valores também são enviados nos headers legados X-Command-Signature, X-Command-Timestamp, X-Command-Algorithm e X-Command-Event. Prefira os headers X-KaironPay-*.

Validação da assinatura (HMAC-SHA256)

A assinatura é calculada sobre o corpo bruto da requisição, com o token do webhook como chave:

# pseudo-código timestamp = header["X-KaironPay-Timestamp"] # unix seconds signedString = timestamp + "." + rawRequestBody # "<ts>.<corpo JSON>" expected = hex( HMAC_SHA256(key = webhookToken, msg = signedString) ) valido = constantTimeEquals(expected, header["X-KaironPay-Signature"])
Importante: valide sobre o corpo bruto (antes de fazer parse do JSON). O timestamp usado na assinatura é o do header X-KaironPay-Timestamp, não o campo timestamp do corpo (que nem sempre existe).

Validação do token

Sempre valide o header X-Webhook-Token antes de processar qualquer evento. Use comparação de tempo constante para evitar timing attacks.

# Simule um webhook (shape real: data.data aninhado, valores em centavos): curl -X POST https://seusite.com/webhook/kaironpay \ -H "Content-Type: application/json" \ -H "X-Webhook-Token: seu_token_secreto" \ -d '{"event":"PayInCompleted","data":{"data":{"correlationID":"order-123","amount":1500,"status":"paid"}}}'
// Express.js — handler completo com validação const express = require('express'); const crypto = require('crypto'); const app = express(); app.use(express.json()); /** * Comparação de tempo constante para evitar timing attacks. * Importante mesmo com tokens simples. */ function safeCompare(a, b) { if (!a || !b || a.length !== b.length) return false; return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); } app.post('/webhook/kaironpay', (req, res) => { const token = req.headers['x-webhook-token']; if (!safeCompare(token, process.env.KAIRONPAY_WEBHOOK_TOKEN)) { return res.status(401).json({ error: 'Unauthorized' }); } // Responda 200 imediatamente res.status(200).json({ received: true }); // Processe de forma assíncrona (não bloqueie a resposta) setImmediate(() => processWebhook(req.body)); }); async function processWebhook({ event, data }) { // PayIn*/PayOut*/DisputeCanceled aninham em data.data; PayInCreated/DisputeCreated usam data direto const d = data.data || data; switch (event) { case 'PayInCompleted': await fulfillOrder(d.correlationID, d.amount); break; // amount em CENTAVOS case 'PayInRefunded': await handleRefund(d.correlationID); break; case 'PayOutCompleted': await onPayoutDone(d.referenceCode); break; case 'PayOutFailed': await onPayoutFail(d.referenceCode); break; default: console.log('Evento não tratado:', event); } }
# Flask webhook handler from flask import Flask, request, jsonify, abort import hmac, os, threading app = Flask(__name__) WEBHOOK_TOKEN = os.environ['KAIRONPAY_WEBHOOK_TOKEN'] @app.route('/webhook/kaironpay', methods=['POST']) def webhook(): token = request.headers.get('X-Webhook-Token', '') # Comparação de tempo constante if not hmac.compare_digest(token, WEBHOOK_TOKEN): abort(401) payload = request.get_json() # Processe em thread separada para responder rápido threading.Thread(target=process_event, args=(payload,), daemon=True).start() return jsonify(received=True), 200 def process_event(payload): event = payload.get('event') # PayIn*/PayOut* aninham em data.data; PayInCreated/DisputeCreated usam data direto outer = payload.get('data', {}) d = outer.get('data', outer) if event == 'PayInCompleted': order_id = d['correlationID'] # amount vem em CENTAVOS. Idempotência: só processe se não processou antes if not db.is_order_paid(order_id): db.mark_order_paid(order_id) send_confirmation_email(order_id) elif event == 'PayInRefunded': db.refund_order(d['correlationID']) elif event == 'PayOutCompleted': db.mark_payout_done(d['referenceCode'])
<?php // webhook.php $token = $_SERVER['HTTP_X_WEBHOOK_TOKEN'] ?? ''; $expected = getenv('KAIRONPAY_WEBHOOK_TOKEN'); // hash_equals usa comparação de tempo constante if (!hash_equals($expected, $token)) { http_response_code(401); exit(json_encode(['error' => 'Unauthorized'])); } $payload = json_decode(file_get_contents('php://input'), true); $event = $payload['event'] ?? ''; // PayIn*/PayOut* aninham em data.data; PayInCreated/DisputeCreated usam data direto $outer = $payload['data'] ?? []; $data = $outer['data'] ?? $outer; // Responder 200 imediatamente e fechar conexão http_response_code(200); header('Content-Type: application/json'); echo json_encode(['received' => true]); if (function_exists('fastcgi_finish_request')) fastcgi_finish_request(); switch ($event) { case 'PayInCompleted': $orderId = $data['correlationID']; // $data['amount'] vem em CENTAVOS // Idempotência: UPDATE só muda se status ainda não for 'paid' $stmt = $pdo->prepare( "UPDATE orders SET status='paid', paid_at=NOW() WHERE correlation_id=? AND status!='paid'" ); if ($stmt->execute([$orderId]) && $stmt->rowCount() > 0) { sendConfirmationEmail($orderId); } break; case 'PayInRefunded': $pdo->prepare("UPDATE orders SET status='refunded' WHERE correlation_id=?")->execute([$data['correlationID']]); break; case 'PayOutCompleted': markPayoutDone($data['referenceCode']); break; }
package main import ( "crypto/subtle" "encoding/json" "log" "net/http" "os" ) func webhookHandler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Webhook-Token") expected := os.Getenv("KAIRONPAY_WEBHOOK_TOKEN") // Comparação de tempo constante if subtle.ConstantTimeCompare([]byte(token), []byte(expected)) != 1 { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } var payload map[string]interface{} json.NewDecoder(r.Body).Decode(&payload) // Responder 200 imediatamente w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"received":true}`)) // Processar em goroutine go func() { event := payload["event"].(string) outer := payload["data"].(map[string]interface{}) // PayIn*/PayOut* aninham em data.data; PayInCreated/DisputeCreated usam data direto d := outer if inner, ok := outer["data"].(map[string]interface{}); ok { d = inner } switch event { case "PayInCompleted": log.Printf("Pedido %v pago! ", d["correlationID"]) // d["amount"] em CENTAVOS fulfillOrder(d["correlationID"].(string)) case "PayInRefunded": handleRefund(d["correlationID"].(string)) case "PayOutCompleted": markPayoutDone(d["referenceCode"].(string)) } }() }
# Sinatra webhook handler require 'sinatra' require 'json' WEBHOOK_TOKEN = ENV['KAIRONPAY_WEBHOOK_TOKEN'] post '/webhook/kaironpay' do token = request.env['HTTP_X_WEBHOOK_TOKEN'].to_s # Comparação de tempo constante (Ruby 2.7+) unless ActiveSupport::SecurityUtils.secure_compare(token, WEBHOOK_TOKEN) halt 401, { error: 'Unauthorized' }.to_json end payload = JSON.parse(request.body.read) event = payload['event'] # PayIn*/PayOut* aninham em data.data; PayInCreated/DisputeCreated usam data direto outer = payload['data'] || {} data = outer['data'] || outer status 200 content_type :json body({ received: true }.to_json) Thread.new do case event when 'PayInCompleted' puts "Pedido #{data['correlationID']} pago! (amount em centavos: #{data['amount']})" fulfill_order(data['correlationID']) when 'PayInRefunded' then handle_refund(data['correlationID']) when 'PayOutCompleted' then mark_payout_done(data['referenceCode']) end end end
Estrutura real dos payloads — leia com atenção:
  • Os eventos PayInCompleted, PayInRefunded, PayOut* e DisputeCanceled aninham os dados em data.data (dois níveis). Use payload.data.data.
  • PayInCreated e DisputeCreated usam data em um nível só.
  • Os eventos PayIn* não incluem um campo timestamp no corpo (use o header X-KaironPay-Timestamp). Os PayOut* incluem.
  • O identificador da cobrança é correlationID (não id).

Payload — TRANSACTION_COMPLETED (PayInCompleted)

Enviado quando um pagamento PIX é confirmado. Este é o evento mais importante — use-o para liberar o produto/serviço. Todos os valores em centavos. Para boletos, event é BoletoCompleted e type é BOLETO.

{ "event": "PayInCompleted", "data": { "data": { "txid": "order-456-uuid-aqui-1234", "correlationID": "order-456-uuid-aqui-1234", "value": 1500, "amount": 1500, "netAmount": 1455, "feeAmount": 45, "status": "paid", "type": "PIX", "paidAt": "2026-06-07T10:02:00.000Z", "createdAt": "2026-06-07T10:00:00.000Z", "customer": { "name": "João Silva", "taxID": "12345678900", "email": "joao@email.com" }, "payer": { "name": "João Silva", "taxID": "12345678900", "bank": "Nubank", "bankIspb": "18236120", "branch": "0001", "account": "12345678" }, "endToEndId": "E123456782026060710020000001", "provider": "OASYFY", "description": "Pedido #456 - Produto Premium" } } }

Payload — PAYOUT_COMPLETED (PayOutCompleted)

Enviado quando um saque é concluído. PayOutFailed e PayOutRefunded têm a mesma estrutura (o PayOutFailed inclui errorMessage).

Unidades mistas neste payload: value vem em centavos, mas amount, netAmount e feeAmount vêm em reais. (Diferente do PayInCompleted, onde tudo é centavos.) O status vem em MAIÚSCULAS e o event é repetido dentro de data.
{ "event": "PayOutCompleted", "timestamp": "2026-06-07T10:05:00.000Z", "data": { "event": "PayOutCompleted", "data": { "id": "a1b2c3", "correlationID": "payout-comissao-vendedor-001", "referenceCode": "payout_x7k9m2a1b3c4d5e6", "value": 5000, "amount": 50.00, "netAmount": 50.00, "feeAmount": 1.50, "status": "COMPLETED", "pixKey": "joao****email.com", "pixKeyType": "email", "recipientName": "João Silva", "recipientDocument": "12345678900", "provider": "Rapdyn", "providerTransactionId": "tx_9988", "endToEndId": "E123456782026060710050000001", "completedAt": "2026-06-07T10:05:00.000Z", "createdAt": "2026-06-07T10:00:00.000Z", "source": "API" } } }

Retentativas e configurações

ConfiguraçãoValor
Timeout por tentativa5 segundos
Retentativas em falhaAté 3 vezes com backoff exponencial
Resposta esperadaHTTP 2xx (200, 201, 204)
Reenvio manualDisponível no painel (Webhooks → Histórico → Reenviar)
Limite de URLsMúltiplas URLs por conta, cada uma com token independente
Idempotência: Seu endpoint pode receber o mesmo evento mais de uma vez (em caso de falha de rede antes do 2xx). Sempre use data.data.correlationID (cobranças) ou data.data.referenceCode (saques) como chave de idempotência no seu banco de dados.
Responda rápido: Seu handler deve retornar HTTP 2xx em até 5 segundos. Processe tarefas demoradas de forma assíncrona (fila, goroutine, thread, setImmediate).

Todos os Eventos

Referência completa de todos os eventos que podem ser recebidos via webhook, com payloads de exemplo.

Eventos de Cobrança / Disputa (PIX In)

Evento (X-KaironPay-Event)payload.eventQuando é disparado
TRANSACTION_CREATEDPayInCreatedCobrança PIX criada
TRANSACTION_COMPLETEDPayInCompleted / BoletoCompletedPagamento PIX ou boleto confirmado ✅
TRANSACTION_REFUNDEDPayInRefundedPagamento estornado ao pagador
DISPUTE_CREATEDDisputeCreatedDisputa/MED aberta — cobrança fica bloqueada (assine TRANSACTION_CHARGEBACK)
DISPUTE_CANCELEDDisputeCanceledDisputa resolvida/cancelada — cobrança desbloqueada (assine TRANSACTION_CHARGEBACK)
Não são enviados: não há webhook de expiração (PayInExpired) nem de cancelamento (PayInCancelled) de cobrança. Uma cobrança expirada/cancelada apenas muda de status internamente — consulte via GET /charges/:correlationID se precisar.

Eventos de Saque (PIX Out)

Evento (X-KaironPay-Event)payload.eventQuando é disparado
PAYOUT_COMPLETEDPayOutCompletedSaque concluído e PIX enviado ✅
PAYOUT_FAILEDPayOutFailedSaque falhou (chave inválida, devolvido etc.) — inclui errorMessage
PAYOUT_REFUNDEDPayOutRefundedSaque devolvido pelo destinatário
Não são enviados: atualmente não há webhooks de criação (PayOutCreated), aprovação (PayOutApproved) nem rejeição (PayOutRejected) de saque. Acompanhe o status via GET /payouts/:reference_code.

Payloads completos por evento

PayInCreated

Nível único de data. amount em reais, status = "pending".

{ "event": "PayInCreated", "timestamp": "2026-06-07T10:00:00.000Z", "data": { "id": "order-456-uuid-aqui-1234", "txid": "order-456-uuid-aqui-1234", "payer": { "name": "João Silva", "account_bank": null, "account_ispb": null, "document_number": "12345678900" }, "amount": 15.00, "status": "pending", "paidAt": null, "refundedAt": null, "endToEndId": null, "provider": "OASYFY", "comment": "Pedido #456" } }

PayInRefunded

Aninhado em data.data. Valores em centavos, status = "refunded".

{ "event": "PayInRefunded", "data": { "data": { "txid": "order-456-uuid-aqui-1234", "correlationID": "order-456-uuid-aqui-1234", "amount": 1500, "refundedAmount": 500, "status": "refunded", "refundedAt": "2026-06-07T12:00:00.000Z", "customer": { "name": "João Silva", "taxID": "12345678900" }, "provider": "OASYFY" } } }

PayOutFailed

Mesma estrutura do PayOutCompleted (aninhado em data.data, unidades mistas), com status: "FAILED" e errorMessage.

{ "event": "PayOutFailed", "timestamp": "2026-06-07T10:01:00.000Z", "data": { "event": "PayOutFailed", "data": { "referenceCode": "payout_x7k9m2a1b3c4d5e6", "value": 5000, "amount": 50.00, "feeAmount": 1.50, "status": "FAILED", "pixKey": "joao****email.com", "pixKeyType": "email", "provider": "Rapdyn", "errorMessage": "Chave PIX inválida", "createdAt": "2026-06-07T10:00:00.000Z", "source": "API" } } }

DisputeCreated

Nível único de data (não data.data). Header X-KaironPay-Event: DISPUTE_CREATED — assine TRANSACTION_CHARGEBACK. Valores em centavos.

{ "data": { "correlationID": "order-456-uuid-aqui-1234", "endToEndId": "E123456782026060710020000001", "value": 1500, "disputeValue": 1500, "status": "OPENED", "reason": "FRAUD", "provider": "OASYFY", "chargeStatus": "BLOCKED" }, "event": "DisputeCreated", "timestamp": "2026-06-07T14:00:00.000Z" }

Códigos de Erro

A API usa códigos HTTP padrão. O body de erro contém success: false, um error (mensagem curta legível — não é um slug) e, quando aplicável, message e details.

Formato do erro

{ "success": false, "error": "Insufficient balance", "message": "Saldo insuficiente para realizar o saque", "details": { "available_balance": 50.00, "requested_amount": 100.00 } }
Erros de validação (error: "Validation error") trazem details com a lista de campos inválidos. Nos erros de saque, os valores em details vêm em reais.

Tabela de erros

Statuserror (valor real)Descrição e ação sugerida
400Validation errorCampos obrigatórios ausentes ou com formato inválido. details lista os campos.
400Insufficient balanceSaldo insuficiente para o saque. details: available_balance vs requested_amount (reais).
400Invalid PIX keyChave PIX em formato inválido para o pixKeyType informado.
400Withdrawal limit exceededSaque acima do limite. details: requested_amount, withdrawal_limit.
400Ticket limit exceededValor da cobrança fora dos limites mín./máx. da conta.
400Not supportedOperação não suportada pelo provedor da conta (ex: delete/refund).
401Missing Authorization header / Invalid API keyAutenticação. Envie Authorization: Bearer sk_... com chave válida.
403Withdrawals blocked / Account blockedSaques desabilitados ou conta bloqueada. Contate o suporte.
404Charge not found / Payout not foundRecurso não encontrado. Verifique o correlationID ou reference_code.
409Duplicate requestcorrelationID reutilizado em saque/transferência. Resposta traz existing_reference_code.
500Failed to … / Internal server errorErro interno. Retente após alguns segundos. Se persistir, abra um ticket.
O valor de error é uma mensagem legível, não um código estável. Para lógica de negócio, prefira ramificar pelo status HTTP (e por details quando presente).

Tratamento de erros em código

# Use -w para ver o HTTP status code curl -s -w "\nHTTP_STATUS:%{http_code}" \ -X POST https://api.kaironpay.com/api/v1/client/payouts \ -H "Authorization: Bearer ${API_KEY}" \ -H "Content-Type: application/json" \ -d '{"value": 999999999, "pixKey": "invalido", "pixKeyType": "email"}'
try { const payout = await kpFetch('/payouts', { method: 'POST', body: JSON.stringify({...}) }); } catch (err) { // kpFetch lança erro com os campos do body de erro switch (err.error) { case 'Insufficient balance': console.error(`Saldo insuficiente. Disponível: R$ ${err.details.available_balance}`); // já em reais break; case 'Invalid PIX key': console.error('Chave PIX inválida — verifique o formato'); break; default: console.error('Erro:', err.error, '-', err.message); } }
try: payout = kp_request('POST', '/payouts', json={...}) except requests.HTTPError as e: err = e.response.json() if err['error'] == 'Insufficient balance': available = err['details']['available_balance'] # já em reais print(f'Saldo insuficiente. Disponível: R$ {available:.2f}') elif err['error'] == 'Invalid PIX key': print('Chave PIX inválida — verifique o formato') else: print(f'Erro ({err["error"]}): {err.get("message", "")}')
try { $payout = kpRequest('POST', '/payouts', [...]); } catch (RuntimeException $e) { $msg = $e->getMessage(); // Extraia o body de erro do message preg_match('/{.*}/', $msg, $matches); if ($matches) { $err = json_decode($matches[0], true); switch ($err['error']) { case 'Insufficient balance': echo 'Saldo insuficiente: R$ ' . number_format($err['details']['available_balance'], 2); // já em reais break; case 'Invalid PIX key': echo 'Chave PIX inválida'; break; } } }
type KaironPayError struct { Error string `json:"error"` Message string `json:"message"` Details map[string]interface{} `json:"details"` } func handleAPIError(statusCode int, body []byte) { var apiErr KaironPayError json.Unmarshal(body, &apiErr) switch apiErr.Error { case "Insufficient balance": log.Printf("Saldo insuficiente (reais): %v", apiErr.Details["available_balance"]) case "Invalid PIX key": log.Println("Chave PIX inválida") default: log.Printf("Erro KaironPay %d (%s): %s", statusCode, apiErr.Error, apiErr.Message) } }
begin payout = kp_request('POST', '/payouts', { ... }) rescue RuntimeError => e if e.message =~ /{/ err = JSON.parse(e.message.match(/{.*}/)[0]) case err['error'] when 'Insufficient balance' puts "Saldo insuficiente: R$ #{err.dig('details','available_balance')}" # já em reais when 'Invalid PIX key' puts 'Chave PIX inválida' else puts "Erro (#{err['error']}): #{err['message']}" end end end

Limites & Boas Práticas

Rate limits, timeouts e recomendações para integração robusta e de alta disponibilidade.

Rate Limits

Atualmente não há um rate limit fixo aplicado às rotas /api/v1/client, e a API não retorna 429 nem headers X-RateLimit-*. Ainda assim, use as operações de forma responsável — envie correlationID estável para idempotência e implemente backoff em erros 5xx. Limites podem ser introduzidos no futuro; projete seu cliente para tolerar 429 com Retry-After caso venham a existir.

Timeouts recomendados

OperaçãoTimeout sugeridoObservação
POST /charges (criar cobrança)30sCriação pode envolver chamadas ao provedor PIX
GET /charges (listar)10sConsultas rápidas
POST /payouts (criar saque)30sProcessamento pode levar alguns segundos
GET /balance5sConsulta simples
Webhook (seu handler)5sKaironPay aguarda até 5s pela resposta 2xx

Boas práticas de integração

PráticaPor quê?
Use correlationID estávelBaseie no ID do pedido da sua aplicação para que retentativas sejam idempotentes
Nunca exponha a API KeyMantenha em variáveis de ambiente no servidor. Nunca no código-fonte ou em client-side JS
Prefira webhooks a pollingWebhooks são mais eficientes, baratos em rate limit e mais rápidos
Processe webhooks asyncResponda HTTP 200 imediatamente e processe em background/fila
Implemente idempotênciaArmazene o ID do evento processado para evitar processar o mesmo evento duas vezes
Monitore PAYOUT_FAILEDConfigure alertas quando saques falharem para não deixar usuários sem pagamento
Use HTTPS na URL do webhookO KaironPay rejeita URLs HTTP
Teste com o reenvio manualUse Webhooks → Histórico → Reenviar no painel para reproduzir eventos
Exponha o /healthz do webhookFacilita diagnóstico quando o KaironPay não consegue entregar
Log tudoRegistre evento, correlationID, timestamp e resposta de cada webhook recebido

Checklist de produção

Item
KAIRONPAY_API_KEY em variável de ambiente, nunca hardcoded
KAIRONPAY_WEBHOOK_TOKEN armazenado com segurança
URL do webhook em HTTPS
Handler responde em menos de 5 segundos
Idempotência implementada no handler de webhook
Retry com backoff exponencial nas chamadas à API
correlationID baseado no ID do pedido (estável)
Alertas configurados para PayOutFailed e erros 5xx
Logs estruturados com correlationID para rastreabilidade