# TrevoPay — API de Pagamentos PIX (PIX IN + PIX OUT) > Gateway de pagamento PIX via API REST+JSON. Voce cria cobrancas (PIX IN) > e saques (PIX OUT) programaticamente e recebe confirmacoes por webhook > assinado. Valores SEMPRE em centavos (int). Envelope padrao de resposta: > { "success": bool, "data": object|null, "error": object|null }. Site: https://treevopay.com Dashboard: https://treevopay.com/index?pagina=desenvolvedores API base: https://api.treevopay.com Docs: https://treevopay.com/docs Status: https://status.treevopay.com ## Regras essenciais (leia ANTES de enviar a primeira chamada) Estas regras valem para TODO endpoint da API — PIX IN (cobrancas) e PIX OUT (saques). Repetimos elas dentro de cada endpoint para nao precisar voltar. 1. amount SEMPRE em centavos (int). Nunca float, nunca string com virgula. - 1990 = R$ 19,90 - 1616 = R$ 16,16 - 100 = R$ 1,00 - 50000 = R$ 500,00 ARMADILHA COMUM: enviar 19.90 achando que cobra R$ 19,90 — vira 19 centavos (R$ 0,19). Errado. O certo eh 1990. 2. Idempotency-Key obrigatoria em TODO POST que movimenta dinheiro (/v1/charges e /v1/cashouts). String de 8 a 200 chars. Use o id do seu pedido. Mesma key + mesmo corpo = mesma resposta (nunca duplica). 3. Webhook eh a fonte de verdade. Nunca libere acesso ao cliente confiando no retorno do front-end. So libere depois do webhook charge.paid (HMAC-SHA256 validado em tempo constante) ou apos GET /v1/charges/{id}. 4. HTTPS obrigatorio em producao. Chamadas HTTP fora de localhost retornam 400 https_required. Sem excecoes. 5. Datas ISO-8601 UTC (sufixo Z). Moeda sempre "BRL". Campos em camelCase em request e response. Encoding sempre UTF-8. 6. Prefixos de ID: ch_ (cobrancas), bp_co_ (saques), whsec_ (signing secret de webhook), pk_test_/sk_test_/pk_live_/sk_live_ (API keys). ## Visao geral A TrevoPay e um gateway de pagamento focado em PIX — PIX IN (entrada, via cobranca/QR Code) e PIX OUT (saida, via cashout programatico) — para infoprodutores e plataformas SaaS brasileiras. A API segue tres principios: 1. Baseada em intencao. Cada endpoint faz exatamente o que o nome diz. POST /v1/charges cria uma cobranca. POST /v1/cashouts realiza um saque. Sem aliases, sem ambiguidade. 2. Consistente. Toda resposta segue o envelope { data, error, success }. Se success=true, os dados estao em data. Se success=false, a falha esta em error com { code, message }. data e error sao mutuamente exclusivos. 3. Idempotente. POSTs que movimentam dinheiro exigem o header Idempotency-Key. Reenviar com a mesma key + mesmo corpo devolve a MESMA resposta — voce nunca duplica cobranca nem saque. ## Ambientes - Producao: https://api.treevopay.com - Desenvolvimento: http://localhost:1515 (router PHP local) Todos os exemplos abaixo usam a URL de producao. Versao atual: v1. A versao da API e prefixada na URL (/v1/...). Mudancas que quebram compatibilidade sobem a versao (v2). Adicoes nao-disruptivas (novos campos opcionais, novos eventos, novos endpoints) permanecem em v1. ## Quick start (3 passos) 1. Gere uma API Key no painel TrevoPay: Dashboard > Ferramentas > API. Voce recebe um par pk_test_... / sk_test_... (e depois pk_live_ / sk_live_). 2. Faca POST /v1/charges com amount em centavos. A resposta traz o brCode (copia-e-cola PIX) e o brCodeBase64 (PNG inline do QR Code). 3. Cadastre seu webhook em Dashboard > Ferramentas > Webhooks e aguarde o evento charge.paid. Valide a assinatura HMAC antes de liberar o acesso. ## Autenticacao Toda chamada de servidor usa o header: Authorization: Bearer SUA_SECRET Tipos de chave: - pk_test_... : chave publicavel de teste. NAO autentica chamadas server. - sk_test_... : chave secreta de teste. Nunca movimenta dinheiro real. - pk_live_... : chave publicavel de producao. - sk_live_... : chave secreta de producao. Movimenta dinheiro real. Regras de seguranca: - A secret (sk_) aparece SOMENTE UMA VEZ na criacao. Guarde-a no seu cofre de segredos (Doppler, AWS Secrets Manager, GitHub Actions secrets, etc.). - Nunca exponha sk_live_ no frontend, em mobile, em apps desktop, em repositorios publicos ou em logs. - Rotacione chaves comprometidas no painel imediatamente. A chave antiga passa a retornar 401 key_revoked. - A chave determina o ambiente: sk_test_ sempre cria cobranca em mode=test; sk_live_ em mode=live. Nao da pra misturar. ## Escopos (scopes) Cada chave pode ter um subconjunto de escopos. Por padrao a chave criada no painel recebe todos os escopos do plano. Use a API admin para emitir chaves de escopo reduzido (ex.: somente leitura). - CHARGE:CREATE : criar cobrancas PIX - CHARGE:READ : consultar cobrancas - CASHOUT:CREATE : criar saques PIX (cashout) - CASHOUT:READ : consultar saques Chamar um endpoint sem escopo: 403 insufficient_scope. ## Criar cobranca PIX (PIX IN) POST /v1/charges REGRAS CRITICAS PRA PIX IN (resumo — detalhes na secao "Regras essenciais"): - amount em centavos (int). 1990 = R$ 19,90. NUNCA 19.90 ou "19,90". - Idempotency-Key obrigatorio (8-200 chars). Use o id do seu pedido. - HTTPS obrigatorio. So libere acesso pelo webhook charge.paid (HMAC). Headers obrigatorios: - Authorization: Bearer sk_live_... (ou sk_test_...) - Content-Type: application/json - Idempotency-Key: string unica por cobranca, 8 a 200 caracteres Sobre a Idempotency-Key: - Mesma key + mesmo corpo: devolve a MESMA cobranca (nao duplica). - Mesma key + corpo diferente: erro 409 idempotency_conflict. - Outra requisicao em vooo com a mesma key: 409 idempotency_in_progress. - A mesma key e reaproveitavel por 24h; depois desse periodo ela e liberada. - Use seu identificador interno (ex.: id do pedido) como Idempotency-Key. Campos do corpo (JSON): - amount (int, obrigatorio): valor em centavos. Ex.: 1990 = R$ 19,90. minimo: 100 (R$ 1,00) maximo: 100000000 (R$ 1.000.000,00) - description (string, opcional): nome do produto/cobranca. Ate 255 chars. - externalId (string, opcional): seu identificador interno. Ate 120 chars. - customer (object, opcional): - name (string): nome do cliente. - email (string): e-mail valido. - taxId (string): 11 digitos (CPF) ou 14 digitos (CNPJ), apenas numeros. - cellphone (string): DDD + numero, apenas digitos, 10 ou 11 chars. - utm (object, opcional): { source, medium, campaign, term, content } — ecoado de volta no webhook charge.paid (uso analitico). - mode: definido pela chave usada, retornado na resposta. Valores: test | live. Exemplo de requisicao: ```bash curl -X POST https://api.treevopay.com/v1/charges \ -H "Authorization: Bearer sk_live_SUA_CHAVE" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pedido-123" \ -d '{ "amount": 1990, "description": "Curso de PIX", "externalId": "PED-123", "customer": { "name": "Joao Silva", "email": "joao@exemplo.com", "taxId": "12345678909", "cellphone": "11999998888" }, "utm": { "source": "instagram", "medium": "bio" } }' ``` Resposta (201 Created): ```json { "success": true, "data": { "id": "ch_2f1a8c9d4e5b6a7c8d9e0f12", "amount": 1990, "currency": "BRL", "status": "pending", "description": "Curso de PIX", "externalId": "PED-123", "brCode": "00020101021126...520400005303986540519.905802BR6304ABCD", "brCodeBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...", "expiresAt": "2026-05-29T14:00:00Z", "mode": "live" }, "error": null } ``` Campos da resposta: - id : identificador unico da cobranca (prefixo ch_). - amount : echo do valor em centavos. - currency : sempre "BRL". - status : pending | paid | expired | failed | refunded. - description : echo da descricao. - externalId : echo do seu identificador. - brCode : payload EMV PIX. Mostre como "copia e cola". - brCodeBase64: PNG do QR Code, ja embutido como data URI. Pronto pra colocar em sem nova requisicao. - expiresAt : ISO-8601 UTC. Apos esse momento o status vira expired. - mode : test | live (segue a chave usada). ## Consultar cobranca GET /v1/charges/{id} Headers: - Authorization: Bearer sk_test_... ou sk_live_... Use para polling de fallback. O caminho preferencial e webhook — polling existe para casos em que o webhook ainda nao chegou (latencia de rede do PSP) ou para reconciliacao em lote. ```bash curl https://api.treevopay.com/v1/charges/ch_2f1a8c9d4e5b6a7c8d9e0f12 \ -H "Authorization: Bearer sk_live_SUA_CHAVE" ``` Resposta: mesmo formato da criacao, com o status atualizado. ## Status possiveis (cobranca) - pending : aguardando pagamento. Estado inicial. - paid : pagamento confirmado pelo PSP. - expired : prazo do QR expirou sem pagamento (expiresAt < agora). - failed : falha operacional (raro — erro interno do PSP). - refunded : pagamento devolvido ao cliente. O status e monotonico: paid nao volta para pending; expired nao volta para pending. Refund e o unico estado terminal possivel apos paid. ## Criar saque PIX / cashout (PIX OUT) POST /v1/cashouts Saque PIX programatico (B2B/B2C). Util para plataformas que precisam pagar prestadores, afiliados ou repassar valores ao cliente final. REGRAS CRITICAS PRA PIX OUT (resumo — detalhes na secao "Regras essenciais"): - amount em centavos (int, >= 1). 50000 = R$ 500,00. NUNCA float. - pixKey + pixKeyType precisam casar: cpf -> 11 digitos sem mascara cnpj -> 14 digitos sem mascara phone -> so digitos com DDD (ex.: 11999998888) email -> minusculas random -> UUID v4 - Idempotency-Key obrigatorio (8-200 chars). Use o id do payout. - Producao exige bspay_cashout_enabled=1 no painel admin (senao 503). - HTTP 202 != saque feito. Confirmacao real vem por webhook cashout.confirmed. Headers obrigatorios: - Authorization: Bearer sk_live_... (com escopo CASHOUT:CREATE) - Content-Type: application/json - Idempotency-Key: string unica por saque, 8 a 200 caracteres Restricoes em producao: - Cashout em mode=live exige bspay_cashout_enabled=1 no painel admin. Quando desligado: 503 cashout_disabled. - Cashout via API roda atualmente apenas com o driver BSPAY. Outros providers retornam 501 cashout_not_supported. - Sandbox (sk_test_) roda livre — testa fluxo sem mover dinheiro. Campos do corpo (JSON): - amount (int, obrigatorio) : valor em centavos (>= 1). - pixKey (string, obrigatorio) : chave PIX do destinatario, ate 200 chars. - pixKeyType (string, obrigatorio): tipo da chave. Um de: - cpf - cnpj - email - phone - random - externalId (string, opcional) : seu identificador. Ate 64 chars. Quando omitido, geramos um deterministico a partir da Idempotency-Key. - description (string, opcional) : descricao livre. Ate 140 chars. Exemplo: ```bash curl -X POST https://api.treevopay.com/v1/cashouts \ -H "Authorization: Bearer sk_live_SUA_CHAVE" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: saque-afiliado-789" \ -d '{ "amount": 50000, "pixKey": "joao@exemplo.com", "pixKeyType": "email", "externalId": "PAYOUT-789", "description": "Comissao afiliado abril/2026" }' ``` Resposta (202 Accepted): ```json { "success": true, "data": { "id": "bp_co_a1b2c3d4e5f6a7b8", "amount": 50000, "currency": "BRL", "status": "pending", "externalId": "PAYOUT-789", "transactionId": "TXN-PSP-99887766", "pixKey": "joao@exemplo.com", "pixKeyType": "email", "description": "Comissao afiliado abril/2026", "failureReason": null, "mode": "live", "requestedAt": "2026-05-29T14:00:00Z", "confirmedAt": null }, "error": null } ``` O HTTP 202 indica que o saque foi aceito e enviado ao PSP. A confirmacao final (status=confirmed ou status=failed) vem via webhook cashout.*. ## Consultar saque GET /v1/cashouts/{id} ```bash curl https://api.treevopay.com/v1/cashouts/bp_co_a1b2c3d4e5f6a7b8 \ -H "Authorization: Bearer sk_live_SUA_CHAVE" ``` ## Status possiveis (saque) - pending : registrado e enviado ao PSP, aguardando confirmacao. - confirmed : saque liquidado na chave PIX do destinatario. - failed : falha na execucao (ver failureReason). ## Webhooks Cadastre uma URL de webhook em Dashboard > Ferramentas > Webhooks. A cada mudanca de status enviamos um POST assinado para o seu endpoint. A fonte de verdade e o servidor: nunca libere acesso confiando apenas no navegador. Eventos de cobranca: - charge.paid : pagamento confirmado. - charge.expired : cobranca expirou sem pagamento. - charge.failed : falha operacional. - charge.refunded : pagamento devolvido. Eventos de saque: - cashout.confirmed : saque liquidado. - cashout.failed : saque falhou (ver failureReason). Headers enviados em cada entrega: - X-Trevo-Event : nome do evento (ex.: charge.paid). - X-Trevo-Timestamp : epoch em segundos (anti-replay). - X-Trevo-Signature : HMAC-SHA256 em hexadecimal. - X-Trevo-Delivery : id unico da entrega (use para deduplicar). Corpo do webhook charge.paid (JSON completo): ```json { "event": "charge.paid", "createdAt": "2026-05-29T14:01:12Z", "data": { "id": "ch_2f1a8c9d4e5b6a7c8d9e0f12", "externalId": "PED-123", "amount": 1990, "status": "paid", "paidAt": "2026-05-29T14:01:10Z", "utm": { "source": "instagram", "medium": "bio", "campaign": null, "term": null, "content": null }, "customer": { "name": "Joao Silva", "email": "joao@exemplo.com", "taxId": "12345678909", "cellphone": "11999998888" } } } ``` Politica de retry: - 1 tentativa imediata apos o evento. - Retries com backoff exponencial por ate 24h se o seu endpoint nao responder 2xx. - Timeout esperado do seu endpoint: 10s. Responda 2xx rapido e processe em background. - Janela anti-replay do timestamp: 5 minutos (300 segundos). - Idempotente: o mesmo evento pode chegar mais de uma vez. Use X-Trevo-Delivery + uma tabela de deduplicacao no seu lado. Validar a assinatura (PHP): ```php 300) { http_response_code(400); exit; } // assinatura = HMAC-SHA256(timestamp + "." + corpo_cru, signing_secret) $expected = hash_hmac('sha256', $ts . '.' . $body, $secret); // comparacao em tempo constante (nunca use ==) if (!hash_equals($expected, $sig)) { http_response_code(401); exit; } $event = json_decode($body, true); // libere o acesso aqui usando $event['data']['externalId'], etc. http_response_code(200); ``` Validar a assinatura (Node.js): ```js const crypto = require('crypto'); function verifyWebhook(req, secret) { const ts = req.headers['x-trevo-timestamp'] || ''; const sig = req.headers['x-trevo-signature'] || ''; const raw = req.rawBody; // string crua, NAO o JSON parseado if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const expected = crypto.createHmac('sha256', secret) .update(`${ts}.${raw}`) .digest('hex'); // comparacao tempo-constante const a = Buffer.from(expected, 'hex'); const b = Buffer.from(sig, 'hex'); return a.length === b.length && crypto.timingSafeEqual(a, b); } ``` Validar a assinatura (Python): ```python import hmac, hashlib, time def verify_webhook(headers, raw_body: bytes, secret: str) -> bool: ts = headers.get('X-Trevo-Timestamp', '') sig = headers.get('X-Trevo-Signature', '') if not ts.isdigit() or abs(time.time() - int(ts)) > 300: return False msg = f"{ts}.{raw_body.decode('utf-8')}".encode('utf-8') expected = hmac.new(secret.encode('utf-8'), msg, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, sig) ``` ## Sandbox (testar sem dinheiro) Use sk_test_. Crie a cobranca normalmente; o brCode e o QR vem em formato valido (ficticios — nao funcionam no app do banco). Para simular o pagamento e disparar o webhook de saida: ```bash curl -X POST https://api.treevopay.com/sandbox/simulate-payment/ch_2f1a8c9d4e5b6a7c8d9e0f12 \ -H "Authorization: Bearer sk_test_SUA_CHAVE" ``` Parametros opcionais: - ?silent=1 : marca pago sem disparar webhook. Use para testar a reconciliacao ativa (polling GET /v1/charges/{id}). Cashout em sandbox roda livre — mode=test sempre retorna status=confirmed sem bater no PSP. ## Codigo de exemplo — criar cobranca em outras linguagens JavaScript (Node.js, fetch nativo): ```js const res = await fetch('https://api.treevopay.com/v1/charges', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_SUA_CHAVE', 'Content-Type': 'application/json', 'Idempotency-Key': 'pedido-123', }, body: JSON.stringify({ amount: 1990, description: 'Curso de PIX', externalId: 'PED-123', customer: { name: 'Joao Silva', email: 'joao@exemplo.com' }, }), }); const { data, error } = await res.json(); if (error) throw new Error(error.message); console.log(data.brCode); ``` Python (requests): ```python import requests, uuid resp = requests.post( 'https://api.treevopay.com/v1/charges', headers={ 'Authorization': 'Bearer sk_live_SUA_CHAVE', 'Content-Type': 'application/json', 'Idempotency-Key': f'pedido-{uuid.uuid4()}', }, json={ 'amount': 1990, 'description': 'Curso de PIX', 'externalId': 'PED-123', }, timeout=15, ) resp.raise_for_status() print(resp.json()['data']['brCode']) ``` PHP (cURL): ```php true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer sk_live_SUA_CHAVE', 'Content-Type: application/json', 'Idempotency-Key: pedido-123', ], CURLOPT_POSTFIELDS => json_encode([ 'amount' => 1990, 'description' => 'Curso de PIX', 'externalId' => 'PED-123', ]), ]); $res = json_decode(curl_exec($ch), true); echo $res['data']['brCode']; ``` ## Rate limits - 100 requisicoes por minuto por API key. - Ao exceder, a resposta e 429 rate_limited com o header Retry-After (segundos para esperar antes de tentar de novo). - Use jitter (random 0-1s) ao retryar para evitar thundering herd. ## Erros Em erro, data e null e error traz { code, message }. O HTTP status code indica a categoria, o code indica a causa especifica. Codigos por categoria: | HTTP | code | Quando acontece | |------|----------------------------|----------------------------------------------| | 400 | idempotency_key_required | Falta header Idempotency-Key. | | 400 | invalid_json | Body nao e JSON valido. | | 400 | https_required | Chamada via HTTP em ambiente nao-local. | | 401 | invalid_credentials | Chave invalida, malformada ou inexistente. | | 401 | key_revoked | Chave revogada no painel. | | 403 | insufficient_scope | Chave nao tem o escopo (ex.: CHARGE:CREATE). | | 404 | charge_not_found | Cobranca nao existe para esta conta. | | 404 | cashout_not_found | Saque nao existe para esta conta. | | 404 | not_found | Rota inexistente. | | 409 | idempotency_conflict | Mesma key + corpo diferente. | | 409 | idempotency_in_progress | Mesma key em processamento. | | 422 | validation_error | Campo invalido (ex.: amount <= 0). | | 429 | rate_limited | Excedeu 100 req/min. | | 501 | cashout_not_supported | Provider ativo nao suporta cashout via API. | | 503 | cashout_disabled | Cashout live desabilitado no painel admin. | | 5xx | internal_error | Falha interna. Reportar com correlation id. | Exemplo de resposta de erro: ```json { "success": false, "data": null, "error": { "code": "validation_error", "message": "amount must be greater than 0" } } ``` ## Indo para producao Checklist: - [ ] Troque sk_test_ por sk_live_ em todos os ambientes. - [ ] Use sempre HTTPS — nunca aceite webhook em endpoint HTTP. - [ ] Valide a assinatura HMAC do webhook ANTES de qualquer side-effect. - [ ] Use hash_equals / timingSafeEqual / hmac.compare_digest, nunca ==. - [ ] Cheque o X-Trevo-Timestamp (rejeite se desviar > 5min do agora). - [ ] Use X-Trevo-Delivery para deduplicar (webhook pode chegar 2x). - [ ] Use Idempotency-Key em todas as cobrancas (id do pedido funciona). - [ ] Logue o id da cobranca + correlation id para suporte. - [ ] Trate 429 com backoff + jitter; nao retentar 4xx (exceto 429). - [ ] Monitore o status na pagina /status (incidentes em tempo real). ## Boas praticas de seguranca - Nunca exponha sk_live_ no frontend (JS publico, app mobile, repos publicos, logs estruturados que vao pro Datadog/CloudWatch). - Sempre valide o webhook via HMAC-SHA256 em tempo constante. - Utilize HTTPS em producao (impostos pelo nosso /v1/* — vai retornar https_required se chamar via HTTP). - Utilize Idempotency-Key em todas as cobrancas e saques. - Nao confie apenas no frontend para confirmar pagamentos — sempre re-valide via GET /v1/charges/{id} ou via webhook autenticado. - Compare assinaturas com funcoes tempo-constante (hash_equals, timingSafeEqual, hmac.compare_digest). Nunca com == ou ===. - Rotacione chaves periodicamente. Mantenha apenas as chaves em uso ativas. - Restrinja escopo: chaves de leitura usam apenas CHARGE:READ / CASHOUT:READ; chaves de criacao tem o escopo correspondente. ## Convencoes - Datas: ISO-8601 com timezone UTC (sufixo Z). Ex.: 2026-05-29T14:00:00Z. - Valores monetarios: SEMPRE int em centavos. 1990 = R$ 19,90. Nunca enviar float ou string formatada. - Moeda: sempre "BRL" (only PIX). Outras moedas nao sao suportadas. - IDs: - ch_ : cobrancas (charges) - bp_co_: saques (cashouts) - whsec_: signing secret de webhook - pk_test_ / sk_test_ / pk_live_ / sk_live_ : API keys - Casing: camelCase para campos de request/response. snake_case so em campos internos do banco (nunca expostos via API). - Encoding: UTF-8 em request e response. Sempre. ## Suporte - Documentacao web : https://treevopay.com/docs - llms.txt (esta) : https://treevopay.com/llms.txt - Dashboard : https://treevopay.com/index?pagina=desenvolvedores - Webhooks logs : https://treevopay.com/index?pagina=integracoes_webhooks - Status / uptime : https://status.treevopay.com - E-mail suporte : suporte@treevopay.com ## Exemplo cURL minimo (copia-e-cola) ```bash curl -X POST https://api.treevopay.com/v1/charges \ -H "Authorization: Bearer sk_live_SUA_CHAVE" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pedido-123" \ -d '{"amount":1990,"description":"Curso","externalId":"PED-123"}' ```