# API de GRANSE Este arquivo descreve a API de GRANSE para uso por assistentes de programação. Contém tudo que é preciso para integrar: autenticação, endpoints, webhooks e os erros possíveis. URL base: https://bank.granseuk.com/api ## Como usar este documento Você é um assistente ajudando alguém a integrar pagamentos Pix. Leia tudo antes de escrever código. Os pontos marcados com ATENÇÃO são erros que quebram a integração em produção — respeite-os literalmente. --- ## 1. Autenticação Toda chamada leva a chave no header Authorization: Authorization: Bearer sk_live_xxxxx A chave identifica a conta. Não existe parâmetro de conta em nenhuma rota, e uma chave nunca alcança dados de outra. ATENÇÃO: existem TRÊS prefixos. `sk_sbx_` é a chave de SANDBOX (homologação): mesmos endpoints, dinheiro de mentira, nenhuma provedora — veja a seção 2.1. Já `sk_test_` e `sk_live_` operam sobre a conta REAL e movem dinheiro DE VERDADE: para esses dois, o prefixo é só um rótulo para organizar as chaves. Para testar sem risco, use chave `sk_sbx_`. Trate qualquer chave `sk_test_`/`sk_live_` como capaz de mover o dinheiro real da conta. ATENÇÃO: a chave é uma senha. Guarde no servidor. Nunca no código do navegador, nunca no aplicativo, nunca em repositório. Quem tem a chave move o dinheiro da conta. Tentativas de autenticação com chave inválida são limitadas por IP de origem. O limite não muda a resposta: você sempre recebe 401 `invalid_key`, nunca um código diferente — não há nada a tratar de forma especial no seu código por causa disso. ### Restringindo por IP Toda chave pode ter uma lista de IPs autorizados, configurada no painel em Credenciais. Uma chave sem lista (o padrão de toda chave já emitida) continua sem restrição de IP nenhuma. Com a lista preenchida, chamadas de IP fora dela recebem 401 `invalid_key` — a mesma resposta de chave errada, para não revelar a existência da restrição a quem está tentando descobrir o motivo da recusa. ### Escopos Cada chave carrega apenas os escopos que recebeu na criação: - `balance:read` — consultar saldo - `transactions:read` — listar movimentações e ver comprovante - `transfers:write` — transferir entre contas da plataforma - `pix:write` — criar cobrança Pix - `pix:read` — consultar cobrança Pix - `pix:send` — enviar Pix para uma chave externa e pagar Pix por QR Code / copia e cola (saída de dinheiro) - `boleto:write` — criar e consultar cobrança por boleto - `card:write` — criar e consultar cobrança por cartão de crédito (em produção, com o `cardToken` gerado no navegador do pagador) - `boleto:pay` — PAGAR boleto de terceiros com o saldo da conta (saída de dinheiro) - `split:read` — ver a regra de split da conta e o que está livre - `split:write` — configurar para onde o split da conta é dividido (conta e chave Pix) - `payroll:read` — consultar funcionários, folhas, histórico e comprovantes da folha - `payroll:write` — folha de pagamento: cadastrar funcionário, criar/editar/cancelar folha e MANDAR PAGAR (sai dinheiro da conta; ver a seção da folha) Chamar uma rota sem o escopo devolve 403 `insufficient_scope`. Para um cardápio ou loja que só precisa cobrar e confirmar, os dois escopos suficientes são `pix:write` e `pix:read`. --- ## 2. Primeira chamada: confirme a chave Antes de qualquer coisa, verifique se a chave responde: curl https://bank.granseuk.com/api/v1/me \ -H "Authorization: Bearer sk_test_sua_chave" Resposta: { "environment": "test", "scopes": ["pix:write", "pix:read"], "account": { "id": "e3fe4b3f-..." }, "tenant": { "slug": "paguemais" }, "permissions": { "balance": true, "transactions": true, "transfers": true } } --- ## 2.1 Sandbox e homologação O sandbox é a MESMA API atendida por um simulador: dinheiro de mentira, nenhuma provedora, pagamentos que você dispara. Serve para homologar a integração inteira sem mover um centavo. Como funciona: - Crie uma chave com ambiente "Sandbox" (prefixo `sk_sbx_`), no app em Credenciais. Use a MESMA URL base e os MESMOS endpoints — o servidor reconhece o prefixo e responde pelo simulador. Só a chave muda entre homologação e produção. - ATENÇÃO: a API real recusa chave `sk_sbx_` (401) e o simulador recusa chave real nas rotas `/v1/sandbox/...` (403). Uma não alcança a outra. - Webhooks: cadastre um destino marcado como "Destino de sandbox". Eventos de sandbox SÓ chegam a destinos de sandbox, e eventos reais nunca chegam a eles — um pagamento de teste nunca libera um pedido de verdade. - Cada conta nasce com R$ 10.000,00 de mentira. O Pix enviado cobra tarifa fixa de R$ 1,00 (para o total ser diferente do valor); o resto não cobra. - Os dados ficam 30 dias e são apagados. Endpoints do sandbox (os mesmos da produção, com os mesmos corpos e erros): GET /v1/me, GET /v1/balance, GET /v1/transactions, GET /v1/transactions/{id}, POST /v1/pix/charges, GET /v1/pix/charges/{id}, POST /v1/transfers, POST /v1/pix/payouts, POST /v1/pix/qr/quote, POST /v1/pix/qr/pay. Não simula: pagar boleto (/v1/boleto/quote, /pay, /payments), /v1/payment-links, /v1/split, QR Pix dinâmico, prazo de recebimento e tabela de tarifas do white-label. Comandos que só existem no sandbox (exigem chave `sk_sbx_`; envie um corpo, mesmo vazio: `-d '{}'`): - `POST /v1/sandbox/charges/{id}/pay` — confirma uma cobrança Pix, boleto ou cartão em análise → evento `charge.paid`. - `POST /v1/sandbox/charges/{id}/expire` — vence a cobrança → `charge.expired`. - `POST /v1/sandbox/payouts/{id}/complete` — conclui um envio de Pix agora → `pix.payout.completed`. - `POST /v1/sandbox/payouts/{id}/fail` — falha o envio e devolve valor e tarifa → `pix.payout.failed`. - `POST /v1/sandbox/balance/topup` com `{"amount":"500.00"}` — soma saldo de mentira. - `GET /v1/sandbox/test-data` — os valores de teste, em JSON. Valores de teste: - Chaves Pix (tipo EMAIL): `falha@sandbox.test` → envio aceito que FALHA em ~4 s (`pix.payout.failed`, valor e tarifa voltam); `recusa@sandbox.test` → recusado na hora (400 `provider_rejected`, nada debitado). Qualquer outra chave válida → envio aceito que conclui sozinho em ~4 s (`pix.payout.completed`). - Pix enviado devolve `status: "processing"`; acompanhe por webhook ou por `GET /v1/transactions/{id}` (`PENDING` → `SETTLED`/`FAILED`). - O copia e cola de uma cobrança Pix do sandbox pode ser pago por `POST /v1/pix/qr/pay`: a cobrança passa a paga (`charge.paid`). QR dinâmico não é aceito (400 `qr_not_supported`). - Qualquer CPF/CNPJ válido serve para boleto e cartão; inválido devolve 400 `payer_document_invalid`. Roteiro de homologação (o que o integrador deve provar antes de ir para produção): 1. Autenticação: `/v1/me` com chave sandbox; 401 `invalid_key` tratado; 403 `insufficient_scope` tratado. 2. Pix (receber): criar cobrança (201, `status: "open"`, `copyPaste`); confirmar por webhook `charge.paid` (conferindo a assinatura) E por consulta `GET /v1/pix/charges/{id}` (`paid`); expirar (`charge.expired`) e cancelar o pedido; repetir a criação com a mesma `Idempotency-Key` devolve a mesma cobrança; o saldo sobe. 3. Boleto: emitir com nome e documento (`digitableLine`); documento inválido (400); pagar (`charge.paid`, `method: "BOLETO"`); expirar. 4. Cartão: aprovado, recusado e em análise — os três tratados. 5. Pix enviado: enviar (201, `processing`, total = valor + tarifa); desfecho `pix.payout.completed`; falha (`falha@sandbox.test`) com valor e tarifa devolvidos; recusa (`recusa@sandbox.test`); `insufficient_funds`; sem `Idempotency-Key` (400 `missing_idempotency_key`); repetir a mesma chave devolve o mesmo envio e não paga duas vezes. 6. Pix por QR: `/v1/pix/qr/quote`; valor diferente → 400 `qr_amount_changed`; pagar. 7. Webhooks: assinatura HMAC conferida (aviso com assinatura errada é rejeitado); responder 2xx rápido; o mesmo evento repetido não repete o efeito; só os eventos assinados. 8. Extrato: paginar `/v1/transactions`; ler o comprovante de uma movimentação. 9. Produção: só a chave muda (`sk_sbx_` → `sk_live_`, mesma URL); cadastrar o destino de webhook de produção SEM marcar sandbox; tratar erros pelo campo `error`, nunca pelo texto; valores como string decimal; uma transação real de valor baixo antes de divulgar. A versão interativa deste roteiro, com checklist, está na documentação em "Homologação e sandbox". --- ## 3. Endpoints ### GET /v1/me Escopo: nenhum. Confirma a chave e mostra os escopos dela. ### GET /v1/balance Escopo: `balance:read` { "available": "2.99", "held": "0.00", "gross": "2.99", "pending": "0.00", "currency": "BRL" } `pending` é o que já foi pago em boleto ou cartão e ainda está dentro do prazo de recebimento do white-label: é do cliente, mas ainda não pode ser gasto (ver `receivable.released` nos webhooks). Vem `"0.00"` quando o white-label não usa prazo — o padrão. ### GET /v1/transactions Escopo: `transactions:read` Parâmetros: `limit` (padrão 25, máximo 100), `offset` (padrão 0) { "items": [ { "id": "6dbda7b0-...", "type": "CHARGE_RECEIVED", "status": "PENDING", "direction": "IN", "amount": "49.90", "fee": "0.00", "description": "Pedido #1234", "counterparty": "Maria Silva", "createdAt": "2026-09-10T15:00:14.993Z", "settledAt": null } ], "total": 128, "limit": 25, "offset": 0 } Pagine com `offset`: ainda há página seguinte enquanto `offset + items.length < total`. ### GET /v1/transactions/{id} Escopo: `transactions:read`. Comprovante de uma movimentação. { "id": "9a29cbf2-...", "type": "CHARGE_RECEIVED", "status": "SETTLED", "direction": "IN", "amount": "100.00", "fee": "15.00", "total": "85.00", "description": "Pedido #1234", "counterparty": { "name": "Maria Silva", "document": null }, "createdAt": "2026-09-10T17:34:30.579Z", "settledAt": "2026-09-10T17:34:30.601Z", "correlationId": "57befe60-...", "feeBreakdown": { "total": "15.00" }, "timeline": [ { "status": "SETTLED", "source": "PROVIDER_WEBHOOK", "reason": null, "at": "2026-09-10T17:34:30.601Z" } ] } - `status`: `PENDING` (em andamento), `SETTLED` (concluída), `FAILED` (não aconteceu; se saiu dinheiro, ele já voltou), `CANCELLED` ou `REVERSED`. Para um Pix ou boleto que VOCÊ pagou, é por aqui que se acompanha o desfecho. - `total`: o que esta operação moveu de fato na conta — para quem recebeu, o líquido depois da tarifa; para quem enviou, o valor mais a tarifa que pagou. - `fee` / `feeBreakdown.total`: a tarifa desta operação. Só o total — a composição interna da tarifa não é exposta por esta rota. - `feeBreakdown` vem `null` quando não há tarifa nesta operação. - `timeline`: os eventos que levaram ao status atual, do mais antigo ao mais recente. ### POST /v1/pix/charges Escopo: `pix:write`. Cobrança avulsa: um QR para UM pagamento. ATENÇÃO: isto NÃO cria link de pagamento. A cobrança nasce, é paga e acaba — não tem página pública nem endereço para divulgar. Se o que você quer é um endereço que várias pessoas possam pagar, use POST /v1/payment-links, descrito adiante. Corpo: { "amount": "49.90", "description": "Pedido #1234", "expiresIn": 3600, "payer": { "name": "Maria Silva", "email": "maria@exemplo.com", "document": "12345678901" }, "externalReference": "pedido-1234" } - `amount`: string decimal com duas casas. Obrigatório. - `description`: até 140 caracteres. Opcional — sem ela, o extrato mostra "Cobrança Pix". - `expiresIn`: segundos, de 60 a 2592000. Padrão 86400 (24h). - `payer`: opcional, todos os campos opcionais. - `externalReference`: seu id do pedido, até 120 caracteres. Volta na resposta. Opcional, mas use — é como você liga a cobrança ao pedido no seu sistema. - `split`: opcional, até 5 linhas. A cobrança já nasce dividida — quando for paga, cada linha recebe sua fatia automaticamente, sem chamada extra. Cada linha: { "destination": "12345678901", "percentage": "10" } `destination` é o CPF, CNPJ ou e-mail da conta que recebe (mesmo tenant). `percentage` é a fatia, como string ("10" = 10%). A soma das linhas não pode passar de 100% — o que sobra fica com você. A API resolve cada `destination` na hora: se algum não existir, ou a soma passar de 100%, a chamada inteira falha (nada é criado) com o motivo exato. O split roda sobre o valor líquido, depois da sua própria tarifa — nunca some as taxas na conta de quem recebe. Split precisa estar habilitado na sua conta. Se não estiver, a chamada falha com `split_not_enabled` — fale com o suporte para ativar. Este campo é AVULSO: vale só para esta cobrança, definido na criação. Para uma regra que passa a valer automaticamente sobre tudo que a conta recebe a partir de agora — Pix, boleto pago e transferência interna recebida, não só cobranças feitas com este campo — use PUT /v1/split, descrito mais adiante. Envie `Idempotency-Key` (8 a 128 caracteres `[A-Za-z0-9_.:-]`) para poder repetir a chamada com segurança: reenviar a mesma chave devolve a cobrança já criada em vez de gerar outra. Sem a chave, cada chamada cria uma cobrança nova — é assim que uma tela de checkout sem esse cuidado costuma duplicar cobrança quando o cliente clica duas vezes ou a rede treme. Resposta 201: { "id": "e0dfecf5-af2f-40e5-9729-2e25b9a4184a", "status": "open", "amount": "49.90", "description": "Pedido #1234", "qrCode": "00020101021226900014br.gov.bcb.pix...", "copyPaste": "00020101021226900014br.gov.bcb.pix...", "expiresAt": "2026-09-10T15:30:10.457Z", "externalReference": "pedido-1234" } `qrCode` e `copyPaste` são o MESMO valor: o código copia-e-cola. Para mostrar o QR, gere a imagem a partir dessa string com qualquer biblioteca de QR code. A API não devolve imagem. Se você mandou `split` no corpo, a resposta traz o campo `split` confirmando o que foi resolvido (o id de cada conta destino), para você conferir antes do pagamento acontecer: "split": [ { "destinationSellerId": "7ec3...", "percentage": "10" } ] ### GET /v1/pix/charges/{id} Escopo: `pix:read`. O estado atual da cobrança. { "id": "e0dfecf5-...", "status": "open", "amount": "49.90", "description": "Pedido #1234", "paidAt": null, "expiresAt": "2026-09-10T15:30:10.457Z" } Estados possíveis: - `open` — criada, aguardando pagamento - `paid` — paga e creditada. É o ÚNICO estado que significa dinheiro na conta - `expired` — passou da validade sem pagamento - `cancelled` — cancelada - `refunded` — estornada ATENÇÃO: só `paid` libera o pedido. Qualquer outro estado, inclusive `open`, significa que o dinheiro não entrou. ### POST /v1/pix/payouts Escopo: `pix:send`. Envia Pix para uma chave externa (Pix de SAÍDA). Diferente de POST /v1/transfers (que move entre contas DESTE banco): aqui o dinheiro sai para uma chave Pix de qualquer instituição. Debita a sua conta. ATENÇÃO: exige o header `Idempotency-Key` (8 a 128 caracteres de [A-Za-z0-9_.:-]). Sem ele a resposta é 400 `missing_idempotency_key`. Reenviar a mesma chave devolve o envio já feito em vez de pagar de novo — é o que protege contra clique duplo e retry de rede num pagamento. Corpo: { "amount": "150.00", "pixKey": "maria@exemplo.com", "pixKeyType": "EMAIL", "description": "Pagamento fornecedor" } - `amount`: string decimal com duas casas. Obrigatório. - `pixKey`: a chave Pix do destino. Obrigatório. - `pixKeyType`: um de `CPF`, `CNPJ`, `EMAIL`, `PHONE`, `RANDOM`. - `description`: até 140 caracteres. Opcional. Resposta 201: { "id": "8f1a...", "status": "processing", "amount": "150.00", "fee": "1.20", "total": "151.20", "pixKey": "ma****@exemplo.com", "providerReference": "E1890...", "endToEndId": "E1890..." } - `amount` é o valor enviado; `fee` a tarifa; `total` o que saiu da conta (amount + fee). `pixKey` volta mascarada. - `status` reflete o estado no provedor no momento do envio (`processing`, `completed`…). O desfecho final de um Pix de saída chega pelos webhooks `pix.payout.completed` (enviado) e `pix.payout.failed` (não saiu — o valor e a tarifa voltam à conta sozinhos). ATENÇÃO: esses dois eventos só são entregues a quem os assinou PELO NOME ao cadastrar o destino; um destino sem filtro de eventos não os recebe. O corpo traz `transactionId` (o mesmo `id` desta resposta), `sellerId`, `amount` e `fee` em CENTAVOS. O `transfer.completed` continua sendo só das transferências entre contas da plataforma. Sem webhook, dá para acompanhar consultando `GET /v1/transactions/{id}` com o `id` devolvido: o `status` vai de `PENDING` para `SETTLED` (enviado) ou `FAILED`. Use o webhook como aviso e a consulta como conferência — se um aviso não chegar, a consulta tem a resposta. ATENÇÃO: sem saldo suficiente para `amount` + `fee`, a resposta é 400 `insufficient_funds` e nada é enviado. Se a conta não tem Pix de saída habilitado, é 400 `no_provider`. Se a conta está com as saídas bloqueadas, é 400 `outbound_blocked`. ### GET /v1/split Escopo: `split:read`. Estado atual do split da conta. Mostra quanto o administrador do white-label já reservou (só o percentual — o `destination` daquela fatia NUNCA é revelado à conta, mesmo por API) e quanto sobra livre para a própria conta configurar, além das regras que a própria conta já tem. Resposta 200: { "adminConfigured": false, "adminPercentage": "0.000000", "availablePercentage": "80.000000", "availableByOperation": { "PIX": "80.000000", "TRANSFER": "100.000000", "BOLETO": "100.000000", "CARD": "100.000000", "LINK": "100.000000" }, "adminByOperation": { "PIX": "0.000000", "TRANSFER": "0.000000", "BOLETO": "0.000000", "CARD": "0.000000", "LINK": "0.000000" }, "rules": [ { "id": "1e04...", "destination": "Maria Fornecedora", "percentage": "10.000000", "operations": ["PIX"] } ], "pixRules": [ { "id": "9c2b...", "label": "Fornecedor Pix", "keyType": "CNPJ", "keyMasked": "**********0138", "percentage": "10.000000", "feePayer": "SHARED" } ], "pix": { "enabled": true, "ready": true, "randomKey": true, "minimum": "1.00" } } - `pixRules`: as linhas de split para CHAVE PIX desta conta (a chave nunca vem inteira, só `keyMasked`). - `pix.enabled`: o white-label liberou o split para chave Pix. `pix.ready`: o Pix de saída dele está configurado. `pix.minimum`: menor repasse aceito; fatia menor que isso fica com a conta. - `adminConfigured`: true se o administrador do white-label travou algum percentual nesta conta. - `availablePercentage`: o que ainda está livre em TODAS as entradas (o menor valor de `availableByOperation`). - `availableByOperation` / `adminByOperation`: por tipo de entrada, quanto ainda pode ser usado pela própria conta (100% menos o que o admin reservou, menos o que a conta já configurou) e quanto o admin reservou. Os tipos de entrada são `PIX` (Pix recebido), `TRANSFER` (transferência interna), `BOLETO`, `CARD` (cartão) e `LINK` (qualquer pagamento feito por link de pagamento, seja Pix, boleto ou cartão). - `operations` (em `rules` e `pixRules`): em quais entradas a linha vale; `null` = todas. - `rules`: as linhas que a PRÓPRIA conta configurou (nunca as do admin). ### PUT /v1/split Escopo: `split:write`. Configura a regra PERMANENTE de split da conta. Diferente do campo `split` de POST /v1/pix/charges (que vale só para UMA cobrança): esta regra passa a valer automaticamente sobre TUDO que a conta recebe dali em diante — Pix, boleto pago e transferência interna recebida — sem precisar mandar nada em cada chamada. Corpo: { "lines": [ { "destination": "maria@exemplo.com", "percentage": "10" } ] } ATENÇÃO: substitui a lista INTEIRA, não soma à anterior. Mandar `"lines": []` remove todas as regras da própria conta (sem mexer no que o administrador do white-label configurou, que é um nível separado e não pode ser alterado por esta rota). `destination` é CPF, CNPJ ou e-mail (mesmo tenant); `percentage` é a fatia como string ("10" = 10%). A soma das linhas mais o que o administrador já reservou nunca pode passar de 100%. Cada `destination` é resolvido antes de salvar — se algum não existir ou a soma passar do disponível, a chamada inteira falha (nada é gravado). Resposta 200: { "rules": [ { "id": "1e04...", "destination": "Maria Fornecedora", "percentage": "10.000000" } ] } ### GET /v1/split/lookup Escopo: `split:read`. Confirma um destino antes de salvar a regra. GET /v1/split/lookup?query=maria@exemplo.com Resolve o CPF, CNPJ ou e-mail em `query` para a conta que ele pertence — a mesma confirmação que a tela de split do cliente mostra ("vai enviar para fulano?"). Use antes de PUT /v1/split para não gravar uma regra apontando para o destino errado. Resposta 200: { "sellerId": "8d6d608e-...", "name": "Maria Fornecedora" } ### PUT /v1/split/pix Escopo: `split:write`. Split PERMANENTE para uma chave Pix FORA da plataforma (fornecedor, sócio, outro banco). O dinheiro sai por Pix para a chave. Corpo: { "lines": [ { "label": "Fornecedor Pix", "keyType": "CNPJ", "key": "63473778000138", "percentage": "10", "feePayer": "SHARED" }, { "ruleId": "9c2b...", "label": "Sócio", "percentage": "5" } ] } - Substitui a lista INTEIRA de linhas Pix (até 5 destinos POR TIPO DE ENTRADA). Mandar `"lines": []` remove todas. - `operations` (opcional): em quais entradas a linha vale, uma lista com `PIX`, `TRANSFER`, `BOLETO`, `CARD` e/ou `LINK`. Omitido numa linha que já existe = mantém o que ela tinha; omitido numa linha nova ou `[]` = TODAS as entradas. Ex.: o sócio recebe 30% só do Pix (`["PIX"]`) e outra pessoa 100% só do boleto (`["BOLETO"]`). A soma de 100% e o limite de 5 destinos valem entrada por entrada; a mesma chave pode repetir só em entradas diferentes. - Linha NOVA: `label`, `keyType` (CPF, CNPJ, EMAIL, PHONE ou RANDOM), `key` completa e `percentage`. Para MANTER uma chave que já existe, mande só o `ruleId` (a chave inteira nunca volta nas leituras). - `feePayer` (opcional): quem arca com a tarifa do Pix — `SOURCE` (a sua conta), `DESTINATION` (quem recebe) ou `SHARED` (dividida; padrão). - Em cada tipo de entrada, a soma das linhas (conta + Pix) mais o que o administrador do white-label reservou nunca passa de 100%. Se algo falhar, nada é gravado. - O Pix só sai quando a venda LIQUIDA (boleto e cartão respeitam o prazo de liquidação). Fatia menor que `pix.minimum` não é enviada e fica com a conta. - Só funciona se `pix.enabled` e `pix.ready` forem true em `GET /v1/split`. Senão a chamada falha com 400 `pix_unavailable`. Resposta 200: { "pixRules": [ { "id": "9c2b...", "label": "Fornecedor Pix", "keyType": "CNPJ", "keyMasked": "**********0138", "percentage": "10.000000", "feePayer": "SHARED" } ] } Erros (400, `error` estável, em minúsculas): `pix_unavailable`, `invalid_request` (formato) e os de regra do split (percentual acima do livre, destino repetido, máximo de destinos). ### GET /v1/split/history Escopo: `split:read`. O que já saiu desta conta por split (conta e Pix juntos). GET /v1/split/history?kind=PIX&limit=20&offset=0 `kind` é opcional (`ACCOUNT` ou `PIX`); `limit` vai até 100. Resposta 200 (valores em decimal): { "items": [ { "id": "5a1c...", "kind": "PIX", "createdAt": "2026-10-05T14:02:11.000Z", "amount": "10.00", "fee": "0.40", "total": "10.40", "destination": "Fornecedor Pix", "keyMasked": "**********0138", "status": "SENT" } ], "total": 1, "limit": 20, "offset": 0, "summary": { "sent": "10.00", "fees": "0.40", "pending": 0 } } `status`: `SENT`, `WAITING_RELEASE` (esperando o prazo de liquidação), `QUEUED`, `SENDING`, `FAILED`, `CANCELLED`. O `summary` conta só o que de fato saiu (`SENT`). --- ## Folha de pagamento (`/v1/payroll`) Pague funcionários por Pix, com as mesmas regras, limites e tarifas da folha do portal. Dois escopos: `payroll:read` (só consulta) e `payroll:write` (tudo que altera). **ATENÇÃO — `payroll:write` move dinheiro.** Funcionário com `autoPay: true` (padrão) é pago SOZINHO no dia do pagamento, e uma folha agendada é paga SOZINHA na data. Por isso não existe escopo "só monta": quem cadastra ou agenda já paga. Dê `payroll:write` somente à chave do servidor que precisa disso e guarde-a como guarda uma chave `pix:send`. Antes de tudo, confira se a folha está ligada: `GET /v1/payroll/overview` devolve `availability.enabled` (o white-label liga o serviço e define a tarifa por pagamento) e, quando false, o motivo em `availability.reason`. Não existem pela API (só no painel, com senha): exportar a planilha de funcionários (traria as chaves Pix completas) e importar planilha. A chave Pix completa NUNCA é devolvida: as respostas trazem `pixKeyMasked`. ### GET /v1/payroll/overview Escopo: `payroll:read`. { "availability": { "enabled": true, "reason": null, "feePerPayment": "0.80" }, "employees": { "active": 12, "monthlyPayroll": "30000.00" }, "upcoming": [ { "id": "b7e1...", "kind": "SALARY", "kindLabel": "Salário", "reference": "2026-11", "title": "Salário 11/2026", "scheduledFor": "2026-11-05", "status": "SCHEDULED", "totalAmount": "30000.00", "totalFee": "0.00", "paidCount": 0, "failedCount": 0, "daysUntil": 31 } ], "thisMonth": { "paid": "30000.00", "payments": 12 } } ### Funcionários - `GET /v1/payroll/employees?status=ACTIVE|INACTIVE&q=nome` — escopo `payroll:read` - `GET /v1/payroll/employees/{id}` — detalhe com o histórico de pagamentos — `payroll:read` - `POST /v1/payroll/employees` — cadastra (201, devolve `{ "id": "..." }`) — `payroll:write` - `PUT /v1/payroll/employees/{id}` — atualiza (mesmo corpo do cadastro) — `payroll:write` - `POST /v1/payroll/employees/{id}/terminate` — desliga (corpo opcional `{ "date": "2026-10-31", "reason": "..." }`) - `POST /v1/payroll/employees/{id}/reactivate` — reativa - `DELETE /v1/payroll/employees/{id}` — remove quem nunca recebeu pagamento Corpo do cadastro: { "name": "Maria Souza", "document": "12345678901", "pixKeyType": "CPF", "pixKey": "12345678901", "salary": "2500.00", "payDay": 5, "businessDayRule": "PREVIOUS", "advanceEnabled": true, "advanceDay": 20, "advancePercent": 40, "autoPay": true, "taxMode": "NET", "dependents": 0 } - `pixKeyType`: `CPF`, `CNPJ`, `EMAIL`, `PHONE` ou `RANDOM`. - `salary`: valor LÍQUIDO a pagar (`taxMode: "NET"`) ou salário BRUTO (`taxMode: "CLT"`, e então INSS e IRRF vêm calculados pelas tabelas do ano; FGTS é só informativo). - `payDay`: dia do mês (1 a 31). `businessDayRule`: `PREVIOUS`, `NEXT` ou `EXACT`, para quando o dia não é útil. - `advanceEnabled`/`advanceDay`/`advancePercent`: adiantamento mensal (percentual do salário). - `autoPay`: true = a plataforma gera e paga a folha sozinha no dia. - Trocar a chave Pix ou aumentar o salário vale também para os itens pendentes das folhas ainda não pagas. Item da lista (`GET /v1/payroll/employees`): { "id": "3f0a...", "name": "Maria Souza", "document": null, "pixKeyType": "EMAIL", "pixKeyMasked": "ma****@exemplo.com", "salary": "2500.00", "payDay": 5, "businessDayRule": "PREVIOUS", "advanceEnabled": true, "advanceDay": 20, "advancePercent": "40.00", "autoPay": true, "taxMode": "NET", "dependents": 0, "status": "ACTIVE", "terminatedAt": null, "nextPayment": "2026-11-05" } ### GET /v1/payroll/runs/suggest Escopo: `payroll:read`. O que pagar a cada funcionário ativo em um tipo de folha. GET /v1/payroll/runs/suggest?kind=SALARY&reference=2026-11-05 `kind`: `SALARY`, `ADVANCE`, `THIRTEENTH_FIRST`, `THIRTEENTH_SECOND`, `VACATION`, `BONUS` ou `OTHER`. Para CLT já vêm as linhas de desconto (INSS, IRRF) e o `fgts` informativo. Use para montar o `items` da folha. { "items": [ { "employeeId": "3f0a...", "name": "Maria Souza", "salary": "2500.00", "base": "1500.00", "suggested": "1500.00", "lines": [], "taxMode": "NET", "fgts": null, "note": "Salário menos o adiantamento de 40%" } ] } ### POST /v1/payroll/runs Escopo: `payroll:write`. Cria uma folha para uma data de hoje em diante (até 500 itens). Devolve 201 com `{ "id": "..." }`. { "kind": "SALARY", "title": "Salário 11/2026", "scheduledFor": "2026-11-05", "items": [ { "employeeId": "3f0a...", "amount": "1500.00", "lines": [ { "label": "Bônus", "amount": "100.00" } ] } ] } - `lines`: ajustes opcionais (negativo = desconto, positivo = acréscimo); `amount` do item é o valor-base. - A folha agendada é PAGA SOZINHA em `scheduledFor`. Não precisa chamar "pagar". - O mesmo funcionário não é pago duas vezes no mesmo período e tipo: a segunda tentativa dá 400 `DUPLICATE_PERIOD`. ### Consultar e editar a folha - `GET /v1/payroll/runs?status=...` e `GET /v1/payroll/runs/{id}` — `payroll:read` - `PATCH /v1/payroll/runs/{id}` — corpo `{ "scheduledFor", "title", "note" }` (todos opcionais) - `POST /v1/payroll/runs/{id}/items` — corpo `{ "employeeId": "..." }` (inclui mais um funcionário) - `PUT /v1/payroll/runs/{id}/items/{itemId}` — corpo `{ "amount", "lines", "remove" }` - `POST /v1/payroll/runs/{id}/cancel` Editar e cancelar só vale enquanto a folha não começou a pagar; depois disso a resposta é 400 `NOT_EDITABLE`. `GET /v1/payroll/runs/{id}` devolve a folha, cada item e um `preview`: { "id": "b7e1...", "kind": "SALARY", "title": "Salário 11/2026", "scheduledFor": "2026-11-05", "status": "SCHEDULED", "totalAmount": "1600.00", "totalFee": "0.00", "paidCount": 0, "failedCount": 0, "lastError": null, "editable": true, "items": [ { "id": "d41c...", "employeeId": "3f0a...", "employeeName": "Maria Souza", "pixKeyMasked": "ma****@exemplo.com", "baseAmount": "1500.00", "lines": [ { "label": "Bônus", "amount": "100.00" } ], "amount": "1600.00", "status": "PENDING", "fee": "0.00", "transactionId": null, "failureReason": null, "paidAt": null } ], "preview": { "toPay": "1600.00", "feeEstimated": "0.80", "totalDebit": "1600.80", "balance": "5000.00", "shortfall": null, "feeError": null } } `status` da folha: `SCHEDULED`, `AWAITING_FUNDS` (saldo insuficiente; tenta de novo quando entrar saldo), `PROCESSING`, `COMPLETED`, `PARTIAL`, `FAILED`, `CANCELLED`. `status` do item: `PENDING`, `SENT` (Pix enviado, aguardando o banco), `PAID`, `FAILED` (o banco confirmou que NÃO saiu; valor e tarifa já voltaram à conta), `SKIPPED`. ### POST /v1/payroll/runs/{id}/pay Escopo: `payroll:write`. Paga agora uma folha `SCHEDULED` ou `AWAITING_FUNDS`, sem esperar a data. O dinheiro sai pelo Pix de saída da conta, com os limites e tarifas dela. Se o saldo não bastar, nada é enviado: a folha vai para `AWAITING_FUNDS` e `lastError` diz quanto falta. Chamar duas vezes é seguro (só um processo pega a folha). Resposta 200: a folha, no mesmo formato de `GET /v1/payroll/runs/{id}`. Acompanhe o desfecho pelo `GET`. `POST /v1/payroll/runs/{id}/retry` (também `payroll:write`) repete SÓ os itens que o banco confirmou que não saíram; nunca repete um Pix que pode ter saído. ### Histórico e comprovante - `GET /v1/payroll/history?employeeId=&status=SENT|PAID|FAILED&from=2026-10-01&to=2026-10-31&limit=50&offset=0` — `payroll:read`. Devolve `items`, `totals` (`paid`, `fees`, `count`) e `page`. - `GET /v1/payroll/runs/{id}/items/{itemId}/receipt` — comprovante do pagamento de um item — `payroll:read`. Erros: 401 `missing_credentials`/`invalid_key`; 403 `insufficient_scope`, `key_without_account` e `not_available_by_api` (exportar/importar planilha); 404 para folha ou funcionário que não é da conta; 400 com `code` estável (`NOT_EDITABLE`, `DUPLICATE_PERIOD`, `INVALID_PIX_KEY`, `INVALID_DOCUMENT`...). --- ### POST /v1/payment-links Escopo: `pix:write`. Cria um link de pagamento. Uma página hospedada que fica aberta e recebe de várias pessoas, até ser cancelada ou expirar. Diferente da cobrança avulsa acima. Quando usar cada um: - **Cobrança avulsa** (`/v1/pix/charges`): o pedido já existe no seu sistema e você só precisa do QR. Um pagamento, um QR. - **Link** (`/v1/payment-links`): o valor vai ser divulgado — uma vaquinha, uma mensalidade, um catálogo — e várias pessoas pagam no mesmo endereço. Corpo: { "description": "Mensalidade de setembro", "amount": "99.90", "amountOpen": false, "singleUse": false, "expiresInHours": 720, "requirePayerName": true } - `description`: 2 a 140 caracteres. Obrigatório. - `amount`: string decimal. Omita junto com `amountOpen: true` para quem paga escolher o valor. - `amountOpen`: quando true, quem paga define o valor, dentro de `minAmount` e `maxAmount` se informados. - `singleUse`: true fecha o link no primeiro pagamento. Padrão false. - `maxUses`: fecha depois de N pagamentos. - `expiresInHours`: 1 a 8760 (um ano). - `requirePayerName`, `requirePayerDocument`, `requirePayerEmail`: exigem o dado de quem paga antes de gerar o Pix. Resposta 201: { "id": "4f21c8de-...", "slug": "k3n8vq2p", "url": "https://seu-banco.com/pagar/k3n8vq2p", "description": "Mensalidade de setembro", "amount": "99.90", "amountOpen": false, "singleUse": false, "status": "open" } `url` é o endereço que você manda para quem vai pagar. ### GET /v1/payment-links Escopo: `pix:read`. Os links da conta, com quanto cada um recebeu. { "items": [ { "id": "4f21c8de-...", "slug": "k3n8vq2p", "url": "https://seu-banco.com/pagar/k3n8vq2p", "description": "Mensalidade de setembro", "amount": "99.90", "singleUse": false, "uses": 12, "status": "open", "received": "1198.80", "payments": 12 } ] } ### POST /v1/payment-links/{id}/cancel Escopo: `pix:write`. Fecha o link. Quem abrir depois vê que não está mais disponível. Os pagamentos já recebidos continuam na conta. ### POST /v1/transfers Escopo: `transfers:write`. Transfere entre contas da plataforma. ATENÇÃO: exige o header `Idempotency-Key` (8 a 128 caracteres de [A-Za-z0-9_.:-]). Sem ele a resposta é 400 `missing_idempotency_key`. curl -X POST https://bank.granseuk.com/api/v1/transfers \ -H "Authorization: Bearer sk_live_sua_chave" \ -H "Idempotency-Key: pedido-8842" \ -H "Content-Type: application/json" \ -d '{"amount":"150.00","to":"@apelido","description":"Pagamento"}' - `to`: aceita `@apelido`, `apelido` ou o UUID da conta. --- ## 4. Idempotência Use `Idempotency-Key` em toda operação que move dinheiro. Reenviar a mesma chave — mesma conta, mesmo valor do header — devolve o resultado da primeira chamada em vez de executar de novo. É o que protege contra timeout e clique duplo: sem isso, um app que reenvia a requisição por segurança quando não recebe resposta a tempo pode gerar duas cobranças ou duas transferências para o mesmo pedido. Obrigatório em `POST /v1/transfers`, `POST /v1/pix/payouts` e `POST /v1/pix/qr/pay` — a chamada sem o header é recusada nessas duas rotas. Na rota de cobrança avulsa — `POST /v1/pix/charges` — o header é OPCIONAL, mas fortemente recomendado. A chave vale por conta: duas contas podem usar o mesmo valor de `Idempotency-Key` sem conflito entre si. --- ## 5. Webhooks — confirmação de pagamento Cadastre a URL no painel, em Credenciais. O sistema avisa quando o pagamento acontece, em vez de você perguntar de tempos em tempos. ### Eventos - `charge.paid` — a cobrança foi paga e o valor entrou na conta. É ESTE que autoriza liberar o pedido. Vale para os três métodos (Pix, boleto, cartão) e para os dois caminhos: cobrança avulsa e pagamento feito num link. - `charge.expired` — venceu sem pagamento. Serve para cancelar o pedido em aberto. - `transfer.completed` — uma transferência enviada chegou ao destino. - `pix.payout.completed` — um Pix enviado (por chave ou por QR) chegou ao destino. SÓ chega a quem assina este nome. - `pix.payout.failed` — um Pix enviado não saiu; valor e tarifa já voltaram à conta. SÓ chega a quem assina este nome. - `receivable.released` — o prazo de um boleto/cartão venceu e o valor passou a ficar disponível. SÓ chega a quem assina este nome. - `receivable.anticipated` — o cliente antecipou recebíveis (`amount`, `fee` e `net` em centavos). SÓ chega a quem assina este nome. - `credit.drawn` — alguém usou o limite de crédito. - `loan.approved` — um empréstimo foi aprovado. - `investment.applied` — um aporte foi aplicado. - `consortium.quota.approved` — uma cota de consórcio foi aprovada. - `consortium.contemplated` — uma cota foi contemplada. Para loja ou cardápio, assine apenas `charge.paid` e, se quiser cancelar pedidos sozinho, `charge.expired`. ### O que chega POST https://seu-sistema.com/webhooks content-type: application/json x-webhook-id: 9f2c1a44-... x-webhook-event: charge.paid x-webhook-timestamp: 1789002842 x-webhook-signature: 7b52009b64fd0a2a49e6d8a939753077792b0554... { "id": "9f2c1a44-...", "type": "charge.paid", "createdAt": "2026-09-09T21:14:02.000Z", "data": { "chargeId": "e636775c-...", "sellerId": "c34b87db-...", "amount": "4990", "method": "PIX" } } ATENÇÃO: `data.amount` vem em CENTAVOS (4990 = R$ 49,90), diferente do resto da API, que usa string decimal. `data.chargeId` é o mesmo `id` devolvido na criação. `data.method` diz como foi paga — `PIX`, `BOLETO` ou `CARD` — sem precisar de uma chamada extra só para descobrir isso. ATENÇÃO (prazo de recebimento): se o white-label definiu prazo para boleto ou cartão, `charge.paid` continua avisando que o PAGAMENTO foi confirmado (pode liberar o pedido), mas o valor fica em `pending` (ver GET /v1/balance) até o prazo vencer — aí sai `receivable.released`. Pix sempre cai na hora. Os eventos `pix.payout.completed`, `pix.payout.failed`, `receivable.released` e `receivable.anticipated` têm o mesmo formato, com `data` assim (valores em CENTAVOS): { "transactionId": "8f1a...", "sellerId": "c34b87db-...", "amount": "15000", "fee": "120" } Em `receivable.released` o identificador é `receivableId` e vem `method` (`BOLETO` ou `CARD`); em `receivable.anticipated` vêm `amount`, `fee` e `net`. ### Conferindo a assinatura A assinatura é o HMAC-SHA256 de `{timestamp}.{corpo}` em hexadecimal, com o segredo do destino. O segredo aparece ao cadastrar a URL e pode ser recuperado depois no painel, em Credenciais, no botão "Ver segredo" ao lado do destino. import { createHmac, timingSafeEqual } from "node:crypto"; function confere(corpoBruto, headers, segredo) { const assinatura = headers["x-webhook-signature"]; const timestamp = Number(headers["x-webhook-timestamp"]); // Entrega velha é entrega repetida: recuse. if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false; const esperado = createHmac("sha256", segredo) .update(`${timestamp}.${corpoBruto}`) .digest("hex"); const a = Buffer.from(assinatura); const b = Buffer.from(esperado); return a.length === b.length && timingSafeEqual(a, b); } ATENÇÃO: use o corpo CRU, exatamente como chegou. Se você deixar o framework fazer o parse do JSON e depois reserializar, os bytes mudam e a assinatura nunca bate. No Express, use `express.raw({ type: "application/json" })` nessa rota. No Next.js App Router, use `await request.text()`. Este é o erro mais comum de todos: o webhook chega, a assinatura falha, e o pagamento nunca confirma. ### Regras obrigatórias 1. Responda 200 rápido. Processe depois, em fila. Demorar faz o envio ser considerado falho e reenviado. 2. Espere repetição. O mesmo evento pode chegar duas vezes. Guarde o `x-webhook-id` já processado e ignore repetidos, ou o pedido é liberado duas vezes. 3. Não confie no valor recebido para creditar. Antes de liberar o pedido, confirme com `GET /v1/pix/charges/{id}`. O webhook diz o que olhar; a consulta diz o que é verdade. 4. Use HTTPS. URLs em HTTP não são aceitas. 5. Vinte falhas seguidas desativam o destino. Depois de corrigir seu servidor, reative no painel em Credenciais, botão "Reativar" — o segredo continua o mesmo. --- ## 6. Erros A maioria vem como JSON com `error` (um slug estável, para checar por código) e `message` (texto para log ou depuração, não confie no texto em si — ele pode mudar): 401 missing_credentials Faltou o header Authorization 401 invalid_key Chave inválida, expirada ou revogada 403 insufficient_scope A chave não tem o escopo da rota 403 wrong_host Chave usada no domínio de outro banco 403 key_without_account Chave sem conta associada, não opera dinheiro 400 missing_idempotency_key POST /v1/transfers exige Idempotency-Key 400 invalid_idempotency_key Idempotency-Key fora do formato aceito 400 invalid_request Corpo inválido; veja "details" quando vier 404 not_found POST /v1/payment-links/{id}/cancel: link não existe ATENÇÃO: nem todo 400/404 segue esse formato. Um valor abaixo do mínimo, uma cobrança ou link que não existe, e a maioria das validações de negócio (`POST /v1/pix/charges`, `POST /v1/payment-links`) hoje devolvem o formato padrão do framework: { "statusCode": 400, "error": "Bad Request", "message": "Valor abaixo do mínimo por cobrança: mínimo R$ 5,00" } { "statusCode": 404, "error": "Not Found", "message": "Cobranca nao encontrada" } Nesses casos, `error` é só a categoria HTTP ("Bad Request", "Not Found") — quem for tratar o erro por código deve usar o `statusCode` e ler `message` como texto para mostrar ou logar, não comparar contra um valor fixo. `POST /v1/transfers` é exceção: os erros de negócio saem com `error` estável e minúsculo, vindo direto do código — 400 seller_not_found Destino não existe nesta conta 400 seller_inactive Destino existe mas está inativo 400 same_seller Origem e destino são a mesma conta 400 insufficient_funds Saldo insuficiente para a transferência 400 amount_invalid Valor não passa nas regras de negócio 400 limit_exceeded Excedeu limite de valor ou de operações 400 cross_tenant Destino não pertence a este banco `POST /v1/pix/payouts` também sai com `error` estável e minúsculo: 400 insufficient_funds Saldo insuficiente para o valor mais a tarifa 400 no_provider A conta não tem Pix de saída habilitado 400 provider_rejected O provedor recusou o envio; nada saiu 400 outbound_blocked A conta está com as saídas bloqueadas 400 pending_reconciliation Sem resposta a tempo: o Pix PODE ter saído; não repita com outra chave 400 idempotency_conflict A Idempotency-Key já foi usada por outra conta `POST /v1/pix/qr/quote` e `POST /v1/pix/qr/pay` saem com o mesmo `error` estável, mais os do código Pix: 400 brcode_invalid Não parece um código Pix 400 brcode_malformed Código Pix incompleto ou malformado 400 brcode_crc Código adulterado ou não copiado inteiro (CRC não confere) 400 brcode_currency Só Pix em reais é aceito 400 brcode_amount Valor do código Pix inválido 400 brcode_payload A cobrança do código não pôde ser lida 400 brcode_unreachable Não foi possível consultar o endereço do código 400 brcode_url_blocked Endereço do código não é aceito (só HTTPS público) 400 qr_invalid A chave Pix do QR estático não é válida 400 qr_expired O código expirou 400 qr_not_active O código já foi pago ou não está mais ativo 400 qr_amount_changed O valor mudou desde a consulta; reconfirme com o valor novo 400 qr_amount_editable O código permite alterar o valor (ainda não suportado) 400 qr_not_supported A provedora da conta não paga QR Code 400 amount_required QR sem valor definido: informe "amount" O `split` de `POST /v1/pix/charges` e as rotas `GET/PUT /v1/split` e `GET /v1/split/lookup` saem com o mesmo `error` estável: 400 split_not_enabled Split não está habilitado nesta conta 400 too_many_destinations Mais de 5 linhas em "split"/"lines" 400 exceeds_available_percentage Soma das linhas passa de 100% (ou do que o admin deixou livre) 400 invalid_percentage "percentage" inválido em alguma linha 400 invalid_document "destination" não parece CPF, CNPJ ou e-mail válido 400 recipient_not_found Algum "destination" não existe neste banco 400 recipient_inactive A conta destino existe, mas está encerrada ou suspensa 400 self_split Um "destination" é a própria conta 400 duplicate_destination O mesmo destino aparece duas vezes 400 timeout_ambiguous Sem resposta do provedor a tempo; conciliar antes de reenviar ATENÇÃO sobre `provider_rejected` em cartão: este MESMO slug cobre dois casos diferentes — cartão recusado pela operadora (dados errados, sem limite, cartão bloqueado) e falha técnica na comunicação com o provedor. Hoje não existe um código separado para cada caso; a única forma de diferenciar programaticamente é a própria falta de diferenciação — trate os dois igual (cartão não foi cobrado, cobrança marcada `cancelled`, pode pedir outro cartão ou tentar de novo) e use o texto em `message` só para mostrar ao usuário ou logar, nunca para decidir o fluxo. Se sua integração precisar registrar o motivo exato da recusa para conciliação, guarde o texto de `message` junto do pedido. ATENÇÃO sobre `timeout_ambiguous` em cartão — diferente de `provider_rejected`: aqui o provedor não respondeu a tempo, então NÃO SE SABE se o cartão chegou a ser cobrado do outro lado. A cobrança fica `open`, não `cancelled` — de propósito, para não afirmar um desfecho que não é certo. NÃO tente cobrar de novo automaticamente depois desse erro: confirme com o suporte ou aguarde antes de gerar uma nova tentativa, ou seu cliente final pode acabar sendo cobrado duas vezes se o provedor tiver processado a primeira depois do seu timeout. Outros `error` possíveis nas rotas de boleto e cartão antes mesmo de chamar o provedor (a cobrança nem chega a nascer): 400 amount_required "amount" ausente ou zerado 400 operation_disabled Este tipo de cobrança está desligado para a conta 400 above_limit Valor acima do limite por operação configurado 400 below_limit Valor abaixo do mínimo por operação configurado Não existe hoje um código equivalente para saldo insuficiente em `POST /v1/pix/charges` (que só recebe, nunca debita a própria conta) nem limite de taxa de chamadas (rate limiting) na API. --- ## 7. Formatos - Dinheiro: string decimal com duas casas, `"150.00"`. NUNCA número — ponto flutuante perde centavo. A exceção é `data.amount` no webhook, que vem em centavos como string. - Datas: ISO 8601 em UTC, `"2026-09-06T04:12:00.000Z"`. - Identificadores: UUID. Não presuma ordem nem sequência. --- ## 8. Fluxo completo de uma loja 1. Cliente fecha o pedido no seu sistema. 2. Você chama `POST /v1/pix/charges` com o valor e o `externalReference` do pedido. 3. Guarda o `id` retornado junto do pedido no seu banco. 4. Mostra o `copyPaste` para o cliente, e o QR gerado a partir dele. 5. O cliente paga. 6. Seu endpoint de webhook recebe `charge.paid`. 7. Você confere a assinatura. 8. Você chama `GET /v1/pix/charges/{id}` e confirma `status: "paid"`. 9. Só então libera o pedido. O passo 8 não é opcional. É o que separa "recebi um aviso" de "o dinheiro está na conta". ### Quando o caminho é o link Se o valor é divulgado e várias pessoas pagam no mesmo endereço — uma mensalidade, uma vaquinha —, crie um link com POST /v1/payment-links e divulgue a `url`. Cada pagamento gera seu próprio `charge.paid`, e os passos 6 a 9 valem igual. A diferença é que o link continua aberto depois: um pagamento não o fecha, a menos que `singleUse` seja true. ## Checklist antes de ir para produção - [ ] A chave está no servidor, fora do repositório - [ ] O webhook usa o corpo cru para conferir a assinatura - [ ] O webhook responde 200 antes de processar - [ ] Eventos repetidos são ignorados pelo `x-webhook-id` - [ ] O pedido só é liberado após `GET` confirmar `status: "paid"` - [ ] Transferências, envios de Pix (payouts) e pagamentos por QR enviam `Idempotency-Key` (obrigatório) - [ ] Depois de pagar um Pix (chave ou QR), o desfecho é acompanhado por `GET /v1/transactions/{id}` (`SETTLED`/`FAILED`); não existe webhook para isso - [ ] Pagamento por QR: o valor confirmado pelo usuário vai em `amount`; `qr_amount_changed` reexibe o valor novo e pede outra confirmação - [ ] `pending_reconciliation` nunca dispara um novo pagamento com outra chave - [ ] Cobranças Pix e boleto enviam `Idempotency-Key` (recomendado, evita duplicar) - [ ] Valores tratados como string decimal, nunca float - [ ] Folha: `payroll:write` só na chave do servidor que paga; funcionário com `autoPay` e folha agendada pagam sozinhos no dia - [ ] Folha: antes de agendar, `GET /v1/payroll/overview` com `availability.enabled: true` e saldo para cobrir a folha mais as tarifas (`preview`) - [ ] Split para chave Pix: `pix.enabled` e `pix.ready` true em `GET /v1/split` antes de `PUT /v1/split/pix` - [ ] Homologou no sandbox (chave sk_sbx_) antes de usar chave real - [ ] Ciente de que sk_test_ e sk_live_ movem dinheiro real (só sk_sbx_ é simulada)