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, OramaPay, Woovi e mais

Valores monetários: Todos os valores na API pública (v1/client) são em centavos (integer). Ex: R$ 10,00 = 1000. R$ 1,50 = 150.
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 ou campos obrigatórios ausentes
401Não autenticado — API Key inválida ou ausente
403Sem permissão para este recurso
404Recurso não encontrado
409Conflito — correlationID já utilizado
422Saldo insuficiente ou chave PIX inválida
429Rate limit excedido
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. Simule com: curl -X POST https://seusite.com/webhook/kaironpay \ -H "Content-Type: application/json" \ -H "X-Webhook-Token: seu_token_secreto" \ -d '{ "event": "PayInCompleted", "timestamp": "2026-06-07T12:00:00Z", "data": { "id": "order-1001", "amount": 99.90, "status": "paid", "payer": { "name": "João Silva" } } }'
// 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; switch (event) { case 'PayInCompleted': { const orderId = data.id; // correlationID que você enviou const amountPaid = data.amount; await fulfillOrder(orderId, amountPaid); break; } case 'PayInRefunded': await handleRefund(data.id); break; case 'PayInExpired': await cancelOrder(data.id); 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).toFixed(2)}`); } 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): if event == 'PayInCompleted': order_id = data['id'] amount = data['amount'] # 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:.2f}') elif event == 'PayInExpired': await cancel_order(data['id']) elif event == 'PayInRefunded': await handle_refund(data['id'])
<?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'] ?? ''; $data = $payload['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['id']; $amount = $data['amount']; // 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 'PayInExpired': cancelOrder($data['id']); break; case 'PayInRefunded': handleRefund($data['id']); 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() { switch payload.Event { case "PayInCompleted": orderId := payload.Data["id"].(string) log.Printf("Pedido %s pago! ", orderId) fulfillOrder(orderId) case "PayInExpired": cancelOrder(payload.Data["id"].(string)) case "PayInRefunded": handleRefund(payload.Data["id"].(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'] data = payload['data'] # Responder imediatamente status 200 content_type :json body '{"received":true}' # Processar assíncronamente Thread.new do case event when 'PayInCompleted' order_id = data['id'] puts "Pedido #{order_id} pago!" fulfill_order(order_id) when 'PayInExpired' cancel_order(data['id']) when 'PayInRefunded' handle_refund(data['id']) 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.
Rate Limit: Padrão de 10.000 requisições por janela de 15 minutos. O header X-RateLimit-Remaining indica quantas requisições restam.

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
Tamanho mínimo26 caracteres
FormatoAlfanumérico, hífens e underscores permitidos
EscopoPor tipo de operação (cobranças e saques têm escopos separados)
Geração automáticaSe omitido, a API gera um correlationID no formato KAIYYYYMMDDHHmmSS + uuid
409 ConflictRetornado se o mesmo correlationID for usado em operações diferentes (ex: criar dois saques diferentes com mesmo ID)

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
422NãoSaldo insuficiente ou chave inválida
429SimAguarde o header Retry-After
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 (mín. 26 chars). Gerado automaticamente se omitido.
customerobjectDados do pagador (ver abaixo)
customer.namestringNome completo do pagador
customer.taxIDstringCPF (11 dígitos) ou CNPJ (14 dígitos), apenas números
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 console.log(charge.qrCodeImage); // <img src={charge.qrCodeImage}> console.log(charge.paymentLinkUrl); // Redirecionar usuário
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 print(charge['paymentLinkUrl']) # link de pagamento
$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 echo $charge['paymentLinkUrl']; // link
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) fmt.Println(charge.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)
{ "correlationID": "order-456-uuid-aqui-1234", "value": 1500, "status": "ACTIVE", "brCode": "00020101021226940014br.gov.bcb.pix2572...", "qrCodeImage": "data:image/png;base64,iVBORw0KGgo...", "paymentLinkUrl": "https://app.kaironpay.com/pay/abc123", "expiresAt": "2026-06-08T23:59:59.000Z", "createdAt": "2026-06-07T10:00:00.000Z", "usedProvider": "XPAYTECH" }

Campos da resposta

CampoTipoDescrição
correlationIDstringID único da cobrança
valueintegerValor em centavos
statusstringACTIVE | COMPLETED | EXPIRED | REFUNDED | CANCELLED
brCodestringCódigo PIX copia-e-cola (EMV)
qrCodeImagestringQR Code em base64 PNG — use direto em <img src="...">
paymentLinkUrlstringLink para página de pagamento hospedada pelo KaironPay
expiresAtstringData/hora de expiração ISO 8601
createdAtstringData/hora de criação ISO 8601
paidAtstringData/hora do pagamento (se COMPLETED)
feeAmountintegerTaxa cobrada em centavos (se pago)
netAmountintegerValor líquido recebido em centavos (se pago)
usedProviderstringProvedor PIX utilizado
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.total}, retornados: ${data.charges.length}`);
data = kp_request('GET', '/charges', params={ 'status': 'COMPLETED', 'limit': 50, 'start_date': '2026-06-01T00:00:00Z' }) for charge in data['charges']: print(charge['correlationID'], charge['value'], charge['status'])
$data = kpRequest('GET', '/charges?status=COMPLETED&limit=50&start_date=2026-06-01T00:00:00Z'); foreach ($data['charges'] 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['charges'].each { |c| puts "#{c['correlationID']} — R$ #{c['value'] / 100.0}" }
Resposta (200)
{ "charges": [ { "correlationID": "order-456-uuid-aqui-1234", "value": 1500, "status": "COMPLETED", "comment": "Pedido #456", "createdAt": "2026-06-07T10:00:00Z", "paidAt": "2026-06-07T10:02:00Z", "feeAmount": 45, "netAmount": 1455 } ], "total": 142, "skip": 0, "limit": 50 }
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 charge = await kpFetch('/charges/order-456-uuid-aqui-1234'); if (charge.status === 'COMPLETED') { console.log('Pago!', charge.paidAt); }
charge = kp_request('GET', '/charges/order-456-uuid-aqui-1234') if charge['status'] == 'COMPLETED': print('Pago em', charge['paidAt'])
$charge = kpRequest('GET', '/charges/order-456-uuid-aqui-1234'); if ($charge['status'] === 'COMPLETED') { echo 'Pago em: ' . $charge['paidAt']; }
body, _ := kpRequestWithRetry("GET", "/charges/order-456-uuid-aqui-1234", nil, 3) var charge ChargeResponse json.Unmarshal(body, &charge) fmt.Println(charge.Status)
charge = kp_request('GET', '/charges/order-456-uuid-aqui-1234') puts "Status: #{charge['status']}"
DELETE/api/v1/client/charges/:correlationID

Cancela uma cobrança ativa. A cobrança deve estar com status ACTIVE.

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 cancelled successfully" }
POST/api/v1/client/charges/:correlationID/refund

Estorna total ou parcialmente uma cobrança já paga (COMPLETED). O valor é devolvido ao pagador via PIX.

ParâmetroTipoDescrição
valueintegerobrigatórioValor do reembolso em centavos. Para reembolso total, use o valor original.
commentstringMotivo do reembolso
# Estorno parcial de R$ 5,00 curl -X POST https://api.kaironpay.com/api/v1/client/charges/order-456-uuid-aqui-1234/refund \ -H "Authorization: Bearer sk_sua_api_key" \ -H "Content-Type: application/json" \ -d '{"value": 500, "comment": "Produto com defeito"}'
const refund = await kpFetch('/charges/order-456-uuid-aqui-1234/refund', { method: 'POST', body: JSON.stringify({ value: 500, comment: 'Produto com defeito' }) }); console.log('Estorno:', refund.status);
refund = kp_request('POST', '/charges/order-456-uuid-aqui-1234/refund', json={ 'value': 500, 'comment': 'Produto com defeito' })
$refund = kpRequest('POST', '/charges/order-456-uuid-aqui-1234/refund', [ 'value' => 500, 'comment' => 'Produto com defeito' ]);
body, _ := json.Marshal(map[string]interface{}{"value": 500, "comment": "Produto com defeito"}) kpRequestWithRetry("POST", "/charges/order-456-uuid-aqui-1234/refund", body, 3)
kp_request('POST', '/charges/order-456-uuid-aqui-1234/refund', { value: 500, comment: 'Produto com defeito' })
Resposta (201 Created)
{ "id": "refund_xyz789", "status": "REFUNDED", "value": 500, "correlationID": "order-456-uuid-aqui-1234", "createdAt": "2026-06-07T12:00:00.000Z" }
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 em centavos.

curl https://api.kaironpay.com/api/v1/client/balance \ -H "Authorization: Bearer sk_sua_api_key"
const balance = await kpFetch('/balance'); const availableBRL = (balance.available / 100).toFixed(2); console.log(`Disponível: R$ ${availableBRL}`);
balance = kp_request('GET', '/balance') print(f"Disponível: R$ {balance['available'] / 100:.2f}") print(f"Pendente: R$ {balance['pending'] / 100:.2f}")
$balance = kpRequest('GET', '/balance'); echo 'Disponível: R$ ' . number_format($balance['available'] / 100, 2, ',', '.');
body, _ := kpRequestWithRetry("GET", "/balance", nil, 3) var balance map[string]interface{} json.Unmarshal(body, &balance) fmt.Printf("Disponível: R$ %.2f ", balance["available"].(float64)/100)
balance = kp_request('GET', '/balance') puts "Disponível: R$ #{balance['available'] / 100.0}"
Resposta (200)
{ "available": 250000, "pending": 15000, "reserved": 5000, "total": 270000, "currency": "BRL" }

Campos da resposta

CampoDescrição
availableSaldo disponível para saque imediato (centavos)
pendingValor de cobranças ainda não liquidadas (centavos)
reservedValor retido em reserva financeira (centavos)
totalSoma de available + pending + reserved (centavos)
currencySempre BRL

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
typestringPIX_IN | PIX_OUT | REFUND | ADJUSTMENT
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=PIX_IN&limit=50" \ -H "Authorization: Bearer sk_sua_api_key"
const params = new URLSearchParams({ type: 'PIX_IN', limit: '50' }); const data = await kpFetch(`/transactions?${params}`); data.transactions.forEach(tx => { console.log(`${tx.type} | R$ ${(tx.amount/100).toFixed(2)} | ${tx.description}`); });
data = kp_request('GET', '/transactions', params={'type': 'PIX_IN', 'limit': 50}) for tx in data['transactions']: print(f"{tx['type']} | R$ {tx['amount']/100:.2f} | {tx['description']}")
$data = kpRequest('GET', '/transactions?type=PIX_IN&limit=50'); foreach ($data['transactions'] as $tx) { echo $tx['type'] . ' | R$ ' . number_format($tx['amount'] / 100, 2) . " "; }
body, _ := kpRequestWithRetry("GET", "/transactions?type=PIX_IN&limit=50", nil, 3)
data = kp_request('GET', '/transactions?type=PIX_IN&limit=50') data['transactions'].each { |tx| puts "#{tx['type']} | R$ #{tx['amount']/100.0}" }
Resposta (200)
{ "transactions": [ { "id": "txn_abc123", "type": "PIX_IN", "amount": 1500, "fee": 45, "netAmount": 1455, "description": "Pedido #456", "correlationID": "order-456-uuid-aqui-1234", "endToEndId": "E123456782026060710020000001", "createdAt": "2026-06-07T10:02:00Z" } ], "total": 89, "skip": 0, "limit": 50 }

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 (mín. 26 chars)

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.reference_code, payout.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['reference_code']} | Status: {payout['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['reference_code'] . ' | Status: ' . $payout['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['reference_code']} | #{payout['status']}"
Resposta (201 Created)
{ "reference_code": "payout_x7k9m2a1b3c4d5e6", "amount": 50.00, "net_amount": 48.50, "fee": 1.50, "status": "processing", "message": "Payout enqueued for processing" }
Erros comuns: 422 saldo insuficiente, 422 chave PIX inválida, 403 saques bloqueados na conta, 409 correlationID duplicado.
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 data = await kpFetch('/payouts?status=COMPLETED&limit=50'); console.log(`${data.total} saques concluídos`);
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')
GET/api/v1/client/payouts/:reference_code

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

Resposta (200)
{ "reference_code": "payout_x7k9m2a1b3c4d5e6", "amount": 50.00, "net_amount": 48.50, "fee": 1.50, "status": "completed", "pix_key": "joao@email.com", "pix_key_type": "email", "description": "Pagamento comissão vendedor", "created_at": "2026-06-07T10:00:00Z", "completed_at": "2026-06-07T10:00:05Z" }
Fluxo de status: pending_approvalprocessingcompleted | failed | rejected

Reembolsos

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

Disponibilidade: O endpoint POST /charges/:id/refund é o método preferido. O endpoint POST /refunds também está disponível para compatibilidade.
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.id, refund.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)
{ "id": "refund_xyz789", "status": "REFUNDED", "value": 500, "correlationID": "order-456-uuid-aqui-1234", "createdAt": "2026-06-07T12:00:00.000Z" }

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 configurado no painelwh_tok_abc123xyz...
X-KaironPay-EventTipo do evento (snake_case)TRANSACTION_COMPLETED
X-KaironPay-TimestampTimestamp Unix da entrega1749297600
Sobre o X-Webhook-Token: O token é único por URL de webhook configurada no painel. É diferente da sua API Key. Armazene-o com segurança e use para validar que a requisição veio do KaironPay.

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 para testar seu handler: curl -X POST https://seusite.com/webhook/kaironpay \ -H "Content-Type: application/json" \ -H "X-Webhook-Token: seu_token_do_painel" \ -d '{"event":"PayInCompleted","data":{"id":"order-123","amount":99.90}}'
// 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 }) { switch (event) { case 'PayInCompleted': await fulfillOrder(data.id, data.amount); break; case 'PayInRefunded': await handleRefund(data.id); break; case 'PayInExpired': await cancelOrder(data.id); break; case 'PayOutCompleted': await onPayoutDone(data.referenceCode); break; case 'PayOutFailed': await onPayoutFail(data.referenceCode); break; default: console.log('Evento desconhecido:', 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') data = payload.get('data', {}) if event == 'PayInCompleted': order_id = data['id'] # 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 == 'PayInExpired': db.cancel_order(data['id']) elif event == 'PayOutCompleted': db.mark_payout_done(data['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'] ?? ''; $data = $payload['data'] ?? []; // 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['id']; // 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 'PayInExpired': $pdo->prepare("UPDATE orders SET status='expired' WHERE correlation_id=?")->execute([$data['id']]); break; case 'PayInRefunded': $pdo->prepare("UPDATE orders SET status='refunded' WHERE correlation_id=?")->execute([$data['id']]); 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) data := payload["data"].(map[string]interface{}) switch event { case "PayInCompleted": log.Printf("Pedido %s pago! ", data["id"]) fulfillOrder(data["id"].(string)) case "PayInExpired": cancelOrder(data["id"].(string)) case "PayOutCompleted": markPayoutDone(data["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'] data = payload['data'] status 200 content_type :json body({ received: true }.to_json) Thread.new do case event when 'PayInCompleted' puts "Pedido #{data['id']} pago! R$ #{data['amount']}" fulfill_order(data['id']) when 'PayInExpired' then cancel_order(data['id']) when 'PayInRefunded' then handle_refund(data['id']) when 'PayOutCompleted' then mark_payout_done(data['referenceCode']) end end end

Payload — TRANSACTION_COMPLETED (PayInCompleted)

Enviado quando um pagamento PIX é confirmado. Este é o evento mais importante — use-o para liberar o produto/serviço.

{ "event": "PayInCompleted", "timestamp": "2026-06-07T10:02:00.000Z", "data": { "id": "order-456-uuid-aqui-1234", "txid": "order-456-uuid-aqui-1234", "payer": { "name": "João Silva", "document_number": "12345678900", "account_bank": "Nubank" }, "amount": 15.00, "status": "paid", "paidAt": "2026-06-07T10:02:00.000Z", "endToEndId": "E123456782026060710020000001", "provider": "XPAYTECH", "comment": "Pedido #456 - Produto Premium" } }

Payload — PAYOUT_COMPLETED (PayOutCompleted)

{ "event": "PayOutCompleted", "timestamp": "2026-06-07T10:05:00.000Z", "data": { "referenceCode": "payout_x7k9m2a1b3c4d5e6", "value": 5000, "netAmount": 4850, "feeAmount": 150, "status": "COMPLETED", "pixKey": "joao@email.com", "pixKeyType": "email", "endToEndId": "E123456782026060710050000001", "completedAt": "2026-06-07T10:05:00.000Z" } }

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.id ou data.referenceCode 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 (PIX In)

Evento (X-KaironPay-Event)payload.eventQuando é disparado
TRANSACTION_CREATEDPayInCreatedCobrança PIX criada com sucesso
TRANSACTION_COMPLETEDPayInCompletedPagamento PIX confirmado ✅
TRANSACTION_EXPIREDPayInExpiredCobrança expirou sem pagamento
TRANSACTION_CANCELLEDPayInCancelledCobrança cancelada via API ou painel
TRANSACTION_REFUNDEDPayInRefundedPagamento estornado ao pagador
TRANSACTION_CHARGEBACKPayInChargebackContestação/disputa recebida no banco

Eventos de Saque (PIX Out)

Evento (X-KaironPay-Event)payload.eventQuando é disparado
PAYOUT_CREATEDPayOutCreatedSaque criado, aguardando processamento
PAYOUT_APPROVEDPayOutApprovedSaque aprovado pelo admin (modo manual)
PAYOUT_COMPLETEDPayOutCompletedSaque concluído e PIX enviado ✅
PAYOUT_FAILEDPayOutFailedSaque falhou (chave inválida, saldo insuficiente etc.)
PAYOUT_REJECTEDPayOutRejectedSaque rejeitado pelo admin

Payloads completos por evento

PayInCreated

{ "event": "PayInCreated", "timestamp": "2026-06-07T10:00:00.000Z", "data": { "id": "order-456-uuid-aqui-1234", "value": 1500, "status": "ACTIVE", "comment": "Pedido #456", "brCode": "00020101...", "expiresAt": "2026-06-08T10:00:00.000Z", "createdAt": "2026-06-07T10:00:00.000Z" } }

PayInExpired

{ "event": "PayInExpired", "timestamp": "2026-06-08T10:00:00.000Z", "data": { "id": "order-456-uuid-aqui-1234", "value": 1500, "status": "EXPIRED", "comment": "Pedido #456", "expiredAt": "2026-06-08T10:00:00.000Z" } }

PayInRefunded

{ "event": "PayInRefunded", "timestamp": "2026-06-07T12:00:00.000Z", "data": { "id": "order-456-uuid-aqui-1234", "refundId": "refund_xyz789", "value": 500, "originalValue": 1500, "status": "REFUNDED", "comment": "Produto danificado", "refundedAt": "2026-06-07T12:00:00.000Z" } }

PayOutFailed

{ "event": "PayOutFailed", "timestamp": "2026-06-07T10:01:00.000Z", "data": { "referenceCode": "payout_x7k9m2a1b3c4d5e6", "value": 5000, "status": "FAILED", "failReason": "invalid_pix_key", "failedAt": "2026-06-07T10:01:00.000Z" } }

Códigos de Erro

A API usa códigos HTTP padrão. O body de erro sempre contém os campos error e message.

Formato do erro

{ "error": "insufficient_balance", "message": "Saldo insuficiente para realizar o saque.", "details": { "available": 5000, "requested": 10000 } }

Tabela de erros

StatuserrorDescrição e ação sugerida
400invalid_requestCampos obrigatórios ausentes ou com formato inválido. Verifique o body da requisição.
400invalid_valueValor monetário inválido (ex: negativo, não-inteiro). Certifique-se de enviar centavos.
401auth_missingHeader Authorization não enviado. Adicione Authorization: Bearer sk_....
401invalid_api_keyAPI Key inválida, revogada ou inativa. Verifique no painel.
403forbiddenSem permissão para este recurso. Conta pode estar bloqueada.
403payouts_blockedSaques desabilitados na conta. Contate o suporte.
404not_foundRecurso não encontrado. Verifique o correlationID ou reference_code.
409duplicate_correlation_idcorrelationID já utilizado em outra operação. Para idempotência, busque o recurso existente.
422insufficient_balanceSaldo disponível insuficiente. Verifique details.available vs details.requested.
422invalid_pix_keyChave PIX não encontrada no DICT do Banco Central. Verifique o formato.
422charge_not_paidTentativa de reembolso em cobrança não paga. Status deve ser COMPLETED.
422refund_exceeds_valueValor do reembolso maior que o valor original da cobrança.
429rate_limitLimite de requisições excedido. Aguarde o header Retry-After e tente novamente.
500internal_errorErro interno do servidor. Retente após alguns segundos. Se persistir, abra um ticket.

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 / 100}`); break; case 'invalid_pix_key': console.error('Chave PIX não encontrada no Banco Central'); break; case 'rate_limit': console.error('Rate limit — aguarde e tente novamente'); break; default: console.error('Erro inesperado:', 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'] print(f'Saldo insuficiente. Disponível: R$ {available/100:.2f}') elif err['error'] == 'invalid_pix_key': print('Chave PIX inválida — verifique o formato') elif err['error'] == 'rate_limit': retry_after = e.response.headers.get('Retry-After', '60') print(f'Rate limit — aguarde {retry_after}s') else: print(f'Erro: {err["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']/100, 2); 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: %v", apiErr.Details["available"]) case "invalid_pix_key": log.Println("Chave PIX inválida") case "rate_limit": log.Printf("Rate limit HTTP %d: %s", statusCode, apiErr.Message) default: log.Printf("Erro KaironPay %d: %s", statusCode, 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').to_i / 100.0}" when 'invalid_pix_key' puts 'Chave PIX inválida' else puts "Erro: #{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

LimiteValorHeader de controle
Requisições por janela10.000 req / 15 minX-RateLimit-Limit
Requisições restantesDecresce a cada reqX-RateLimit-Remaining
Reset da janelaTimestamp UnixX-RateLimit-Reset
Quando excedidoHTTP 429 + Retry-AfterRetry-After (segundos)

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