Documentação da API — FlowinPay
API REST para receber PIX no Brasil. Autenticação por chave no header X-Api-Key. Base: https://app.flowinpay.com.br/api/v1.
Versão legível por IA: llms.txt · documentação completa em Markdown.
- Autenticação — App ID (header X-Api-Key), public_id e permissões.
- Cobranças (PIX) — Criar cobranças PIX, com split de pagamento (% ou fixo).
- Pix Recorrente — Assinaturas com débito automático: autorização única, ciclos cobrados sozinhos.
- Saldo — Consultar saldo disponível, bloqueado e resumo financeiro.
- Saques — Solicitar e listar saques via PIX.
- Transações — Extrato de transações e filtros.
- Webhooks — Eventos, payloads e verificação de assinatura HMAC.
- Guia de Webhooks — Passo a passo para receber webhooks (várias linguagens).
- Taxas — Taxas vigentes (2% + R$ 1) e limites.
- Página de Pagamento — Página pública de pagamento PIX.
- Erros — Códigos de erro e tratamento.
Autenticação
Autenticação
A API FlowinPay utiliza uma App ID (API Key) para autenticação. Todas as requisições à API REST (/api/v1/...) devem incluir o header X-Api-Key.
Gerar a App ID
- Acesse o painel em
https://app.flowinpay.com.br/api-keys
- Clique em Nova chave
- Defina um nome e as permissões desejadas
- Copie a App ID gerada (formato:
fpk_xxxxxxxxxxxxxxxx)
Atenção: A App ID completa só é exibida uma vez. Guarde em local seguro.
Como Usar
Inclua a App ID no header X-Api-Key em todas as requisições:
X-Api-Key: fpk_xxxxxxxxxxxxxxxx
Exemplo
curl -X GET "https://app.flowinpay.com.br/api/v1/balance" \
-H "X-Api-Key: fpk_xxxxxxxxxxxxxxxx" \
-H "Accept: application/json"
public_id — recebedor de split
Além da App ID (secreta), sua conta tem um public_id: um identificador público usado para te incluir como recebedor em um split de pagamento de outra conta. Fica no painel em Minha Conta → "ID de recebedor (Split)" e pode ser compartilhado sem risco — ele não autentica nada, serve só para receber. Veja Cobranças → Split de Pagamento.
A App ID é como a sua senha (secreta, no header X-Api-Key); o public_id é como o número da conta para receber split (público). São coisas diferentes.
Permissões
A App ID pode ter escopos específicos:
| Permissão |
Descrição |
charge:read |
Consultar cobranças |
charge:create |
Criar cobranças |
charge:cancel |
Cancelar cobranças |
withdrawal:read |
Consultar saques |
withdrawal:create |
Solicitar saques |
balance:read |
Consultar saldo, extrato e resumo |
webhook:create |
Criar e remover webhooks via API |
* |
Acesso total |
Restrição por IP
Opcionalmente, a App ID pode ser restrita a uma lista de IPs autorizados. Requisições de outros IPs recebem 403.
Autenticação do Painel (login)
O painel administrativo usa um Bearer Token gerado no login — diferente da App ID da API:
curl -X POST "https://app.flowinpay.com.br/api/login" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"email":"usuario@email.com","password":"senha123"}'
Response:
{
"user": {
"id": 1,
"name": "João Silva",
"email": "usuario@email.com"
},
"token": "1|abc123def456..."
}
Use o token retornado no header Authorization: Bearer 1|abc123def456... — apenas para as rotas do painel. As rotas /api/v1/* continuam usando X-Api-Key.
Erros de Autenticação
| Status |
Descrição |
401 |
App ID ausente, inválida ou revogada |
403 |
App ID válida, mas sem permissão para o recurso (ou IP não autorizado) |
429 |
Rate limit excedido |
Cobranças (PIX)
Cobranças (PIX)
Criar Cobrança
Permite requisição de pagamento PIX. Após o processamento, webhooks serão enviados para a callbackUrl fornecida.
POST /api/v1/charges
Headers
| Header |
Valor |
X-Api-Key |
fpk_xxxx |
Content-Type |
application/json |
Accept |
application/json |
Body
| Campo |
Tipo |
Obrigatório |
Descrição |
value |
number |
Sim |
Valor em reais (mín: R$2, máx: R$150) |
description |
string |
Não |
Descrição da cobrança |
callbackUrl |
string |
Não |
URL para receber webhooks (cria webhook automaticamente) |
webhook_url |
string |
Não |
Alias para callbackUrl |
customer_name |
string |
Não |
Nome do pagador |
customer_email |
string |
Não |
Email do pagador |
customer_tax_id |
string |
Não |
CPF/CNPJ do pagador |
customer_phone |
string |
Não |
Telefone do pagador |
split |
array |
Não |
Divisão do pagamento entre até 3 recebedores (ver seção Split de Pagamento) |
Exemplo
curl -X POST "https://app.flowinpay.com.br/api/v1/charges" \
-H "X-Api-Key: fpk_xxxx" \
-H "Content-Type: application/json" \
-d '{
"value": 50.00,
"description": "Pedido #123",
"callbackUrl": "https://seusite.com/webhook/flowinpay",
"customer_name": "João Silva",
"customer_email": "joao@email.com"
}'
Response (200)
{
"message": "Cashin request successfully submitted",
"charge": {
"id": 42,
"correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"value": "50.00",
"fee_value": "2.00",
"status": "active",
"br_code": "00020101021226580014br.gov.bcb.pix...",
"qr_code_image": "https://app.flowinpay.com.br/storage/qr/abc123.png",
"payment_link_url": "https://app.flowinpay.com.br/pay/a1b2c3d4...",
"expires_at": "2026-06-06T13:00:00Z"
}
}
Campos Importantes
| Campo |
Descrição |
br_code |
PIX copia e cola — exiba para o pagador |
payment_link_url |
Link de pagamento — compartilhe com o pagador |
qr_code_image |
URL da imagem do QR Code |
expires_at |
Expiração (geralmente 24h) |
Split de Pagamento (opcional)
Divida o valor de uma cobrança entre até 3 contas FlowinPay, identificadas pelo public_id de cada recebedor (disponível no painel em Minha Conta → "ID de recebedor (Split)").
Campo split
Array de objetos, cada um com:
| Campo |
Tipo |
Descrição |
type |
string |
percentage (porcentagem, 1 a 99) ou fixed (valor fixo em R$) |
value |
number |
A porcentagem (se percentage) ou o valor em reais (se fixed) |
recipient |
string |
O public_id da conta que vai receber |
Regras
- A taxa da FlowinPay (R$ 1 + 2%) é descontada primeiro, sobre o valor cheio.
percentage incide sobre o valor cheio da cobrança; fixed é um valor absoluto.
- O dono da cobrança (você) fica com o restante.
- A soma (taxa + splits) não pode ultrapassar o valor da cobrança.
Exemplo
curl -X POST "https://app.flowinpay.com.br/api/v1/charges" \
-H "X-Api-Key: fpk_xxxx" \
-H "Content-Type: application/json" \
-d '{
"value": 100,
"split": [
{ "type": "percentage", "value": 10, "recipient": "PUBLIC_ID_A" },
{ "type": "fixed", "value": 20, "recipient": "PUBLIC_ID_B" }
]
}'
Para uma cobrança de R$ 100: taxa R$ 3,00 · Recebedor A 10% = R$ 10,00 · Recebedor B fixo = R$ 20,00 · você fica com R$ 67,00. O crédito cai direto no saldo de cada recebedor quando o pagamento é confirmado.
Listar Cobranças
GET /api/v1/charges
Query Parameters
| Parâmetro |
Tipo |
Descrição |
status |
string |
Filtrar por status |
page |
integer |
Página (padrão: 1) |
Response (200)
{
"current_page": 1,
"data": [
{
"id": 42,
"correlation_id": "a1b2c3d4...",
"value": "50.00",
"status": "paid",
"description": "Pedido #123",
"paid_at": "2026-06-05T14:30:00Z",
"created_at": "2026-06-05T13:00:00Z"
}
],
"last_page": 1,
"total": 1
}
Consultar Cobrança
GET /api/v1/charges/{id}
Response (200)
{
"charge": {
"id": 42,
"correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"value": "50.00",
"fee_value": "2.00",
"fee_percent": "2.00",
"status": "paid",
"br_code": "00020101021226580014br.gov.bcb.pix...",
"payment_link_url": "https://app.flowinpay.com.br/pay/a1b2c3d4...",
"description": "Pedido #123",
"customer_name": "João Silva",
"paid_at": "2026-06-05T14:30:00Z",
"expires_at": "2026-06-06T13:00:00Z",
"created_at": "2026-06-05T13:00:00Z"
}
}
Cancelar Cobrança
POST /api/v1/charges/{id}/cancel
Response (200)
{
"message": "Cobrança cancelada com sucesso",
"charge": {
"id": 42,
"status": "cancelled"
}
}
Apenas cobranças com status active podem ser canceladas.
Status das Cobranças
| Status |
Descrição |
pending |
Aguardando processamento |
active |
PIX gerado, aguardando pagamento |
paid |
Pagamento confirmado |
cancelled |
Cancelada pelo usuário |
expired |
Expirada (após 24h) |
Fluxo de Pagamento
- Crie a cobrança → Receba
br_code e payment_link_url
- Envie o link → Compartilhe
payment_link_url com o pagador
- Aguarde confirmação → Use webhooks para receber notificação
- Consulte se necessário → Use
GET /api/v1/charges/{id}
Webhook Automático (callbackUrl)
Ao criar uma cobrança, passe callbackUrl no body — a FlowinPay criará automaticamente um webhook na sua conta apontando para essa URL. Quando o pagamento for confirmado (ou expirar/cancelar), enviaremos um POST para a URL.
Importante: Se a mesma callbackUrl for usada em múltiplas cobranças, apenas um webhook será criado (sem duplicatas). O webhook aparece na aba Integração → Webhooks do painel.
Pix Recorrente
Pix Recorrente (assinaturas)
Assinatura com débito automático. O cliente autoriza uma vez no app do banco dele e os
ciclos seguintes são liquidados sozinhos, sem ele pagar nada de novo.
É diferente de uma cobrança comum: em /charges você gera um PIX e o cliente paga cada um.
Aqui ele autoriza a recorrência e você deixa de depender da ação dele todo mês.
Antes de começar
CPF (ou CNPJ) e nome do cliente são obrigatórios. Não é exigência da FlowinPay: o Banco
Central pede identificação do pagador para autorizar um débito recorrente. Se o seu checkout
hoje não coleta CPF, precisa passar a coletar antes de usar assinatura.
Como funciona
| Passo |
Quem faz |
O que acontece |
| 1 |
Você |
Cria a assinatura e recebe um QR Code de autorização |
| 2 |
Seu cliente |
Lê o QR no app do banco e autoriza a recorrência |
| 3 |
FlowinPay |
Avisa em subscription.authorized — libere o acesso |
| 4 |
FlowinPay |
A cada ciclo, cobra sozinha e avisa em subscription.charged |
Endpoints
| Método |
Rota |
Descrição |
| POST |
/api/v1/subscriptions |
Criar assinatura |
| GET |
/api/v1/subscriptions |
Listar |
| GET |
/api/v1/subscriptions/{id} |
Consultar |
| GET |
/api/v1/subscriptions/{id}/charges |
Ciclos da assinatura |
| DELETE |
/api/v1/subscriptions/{id} |
Cancelar |
Criar assinatura
| Campo |
Tipo |
Obrigatório |
Descrição |
value |
number |
sim |
Valor de cada ciclo, em reais |
interval |
string |
sim |
weekly, monthly, quarterly, semiannual ou annual |
description |
string |
sim |
Até 35 caracteres — é o texto que o cliente vê no app do banco |
customer_name |
string |
sim |
Nome do cliente |
customer_tax_id |
string |
sim |
CPF (11 dígitos) ou CNPJ (14) |
start_date |
string |
não |
Data do primeiro ciclo (YYYY-MM-DD). Precisa ser posterior a hoje — o Banco Central recusa cobrança que vence no mesmo dia em que é criada. Padrão: amanhã |
end_date |
string |
não |
Encerra nesta data. Sem ela, prazo indeterminado |
webhook_url |
string |
não |
URL de callback, igual às cobranças |
O description é a única coisa que o cliente lê na hora de autorizar. Use o nome do produto:
"Clube VIP · mensal" funciona; "assinatura" não diz nada e derruba conversão.
O primeiro ciclo é sempre cobrado junto com a autorização: o QR é composto, e o cliente
paga e autoriza no mesmo gesto. Não há parâmetro para separar as duas coisas.
curl -X POST "https://app.flowinpay.com.br/api/v1/subscriptions" \
-H "X-Api-Key: fpk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"value":29.90,"interval":"monthly","description":"Clube VIP · mensal",
"customer_name":"Maria Souza","customer_tax_id":"12345678909"}'
{
"subscription": {
"id": 17,
"status": "criada",
"valor": "29.90",
"periodicidade": "monthly",
"objeto": "Clube VIP · mensal",
"pagador_nome": "Maria Souza",
"data_inicio": "2026-09-21",
"proximo_ciclo_em": "2026-10-21",
"ciclos_gerados": 1,
"codigo_autorizacao": "00020126870014br.gov.bcb.pix2565pix..."
}
}
O codigo_autorizacao é o Pix copia e cola. Gere o QR a partir dele, ou mande o texto — o
cliente aponta a câmera do banco ou cola, e o banco pede que ele pague o primeiro ciclo e
autorize os seguintes no mesmo gesto.
O documento do pagador (pagador_documento) não volta na resposta: é CPF de terceiro, e não
sai da plataforma.
Enquanto ele não autorizar, a assinatura fica em criada e nada é cobrado. Se o
codigo_autorizacao vier nulo, consulte a assinatura de novo em alguns segundos — o
GET /subscriptions/{id} busca o código na instituição de pagamento quando ele falta.
Status da assinatura
Os valores são estes, em português, exatamente como aparecem no campo status:
| Status |
Significado |
criada |
Esperando o cliente autorizar no banco |
aprovada |
Autorizada — os ciclos são cobrados sozinhos |
rejeitada |
O cliente recusou a autorização |
expirada |
O prazo de autorização passou sem resposta |
cancelada |
Cancelada por você, pelo cliente ou pelo banco dele |
falhou |
A tentativa não chegou à instituição de pagamento; ninguém foi convidado a autorizar |
O cliente pode cancelar a qualquer momento pelo app do banco, sem passar por você. Quando isso
acontece você recebe subscription.canceled — é o sinal para cortar o acesso.
Quando um ciclo falha
Saldo insuficiente não cancela a assinatura. A FlowinPay tenta de novo sozinha — até 3 vezes
em 7 dias a partir do vencimento. Você só recebe subscription.payment_failed depois que
todas as tentativas falharem.
Webhooks
| Evento |
Quando |
O que fazer |
subscription.authorized |
Cliente autorizou |
Liberar o acesso |
subscription.charged |
Ciclo liquidado |
Manter o acesso |
subscription.payment_failed |
Ciclo falhou após as retentativas |
Suspender o acesso |
subscription.canceled |
Assinatura encerrada |
Revogar o acesso |
Os ciclos aparecem no extrato normal
Cada ciclo pago é uma cobrança como qualquer outra: credita saldo, desconta taxa e aparece em
/api/v1/charges e /api/v1/transactions, com o campo subscription_id preenchido.
Quem já concilia por ali não precisa mudar nada — e quem quiser separar assinatura de venda
avulsa é só filtrar por esse campo.
Saldo
Saldo e Resumo Financeiro
Consultar Saldo
GET /api/v1/balance
Response (200)
{
"balance": {
"available": 1250.75,
"blocked": 100.00,
"total": 1350.75
}
}
Campos
| Campo |
Tipo |
Descrição |
available |
number |
Saldo disponível para saque |
blocked |
number |
Saldo bloqueado (saques pendentes) |
total |
number |
Total (available + blocked) |
Exemplo
curl -X GET "https://app.flowinpay.com.br/api/v1/balance" \
-H "X-Api-Key: fpk_xxxx" \
-H "Accept: application/json"
Resumo Financeiro
GET /api/v1/summary
Response (200)
{
"summary": {
"today": {
"income": 1500.00,
"expenses": 200.00,
"fees": 45.00,
"count": 12
},
"week": {
"income": 8500.00,
"expenses": 1200.00,
"fees": 210.00,
"count": 65
},
"month": {
"income": 32000.00,
"expenses": 5000.00,
"fees": 850.00,
"count": 240
},
"counts": {
"charges": 240,
"withdrawals": 5,
"disputes": 2
}
}
}
Exemplo
curl -X GET "https://app.flowinpay.com.br/api/v1/summary" \
-H "X-Api-Key: fpk_xxxx" \
-H "Accept: application/json"
Saques
Saques (CashOut)
Endpoints para solicitar e consultar saques via PIX.
Solicitar Saque
POST /api/v1/withdrawals
Body
| Campo |
Tipo |
Obrigatório |
Descrição |
value |
number |
Sim |
Valor em reais (mín: R$10) |
pix_key |
string |
Sim |
Chave PIX de destino |
pix_key_type |
string |
Sim |
Tipo: cpf, cnpj, email, phone, random |
description |
string |
Não |
Descrição do saque |
Exemplo
curl -X POST "https://app.flowinpay.com.br/api/v1/withdrawals" \
-H "X-Api-Key: fpk_xxxx" \
-H "Content-Type: application/json" \
-d '{
"value": 100.00,
"pix_key": "12345678901",
"pix_key_type": "cpf",
"description": "Saque mensal"
}'
Response (201)
{
"message": "Saque solicitado com sucesso",
"withdrawal": {
"id": 15,
"value": "100.00",
"fee_value": "2.00",
"net_value": "98.00",
"pix_key": "12345678901",
"pix_key_type": "cpf",
"status": "pending",
"description": "Saque mensal",
"created_at": "2026-06-05T15:00:00.000000Z"
}
}
Taxas
| Configuração |
Valor |
| Taxa fixa |
R$2,00 por saque |
| Valor mínimo |
R$10,00 |
| Processamento |
Até 1 hora útil |
Listar Saques
GET /api/v1/withdrawals
Response (200)
[
{
"id": 15,
"value": "100.00",
"fee_value": "2.00",
"net_value": "98.00",
"pix_key": "12345678901",
"pix_key_type": "cpf",
"status": "completed",
"processed_at": "2026-06-05T15:30:00.000000Z",
"created_at": "2026-06-05T15:00:00.000000Z"
}
]
Status dos Saques
| Status |
Descrição |
pending |
Aguardando processamento |
processing |
Em processamento na adquirente |
completed |
Saque concluído |
failed |
Falha no processamento |
cancelled |
Cancelado pelo usuário |
Erros Comuns
| Status |
Erro |
Solução |
| 422 |
Saldo insuficiente |
Verifique o saldo antes de solicitar |
| 422 |
Valor mínimo R$10 |
Aumente o valor do saque |
| 422 |
Chave PIX inválida |
Verifique o tipo e formato da chave |
| 429 |
Rate limit (60/min) |
Aguarde antes de tentar novamente |
Transações
Transações (Extrato)
Listar Transações
GET /api/v1/transactions
Query Parameters
| Parâmetro |
Tipo |
Descrição |
type |
string |
Filtrar por tipo: charge_received, split_received, charge_fee, withdrawal, withdrawal_fee, refund, adjustment |
start_date |
date |
Data inicial (YYYY-MM-DD) |
end_date |
date |
Data final (YYYY-MM-DD) |
min_amount |
number |
Valor mínimo |
max_amount |
number |
Valor máximo |
search |
string |
Buscar por descrição |
page |
integer |
Página |
Response (200)
{
"current_page": 1,
"data": [
{
"id": 150,
"type": "charge_received",
"amount": "48.00",
"balance_before": "1000.00",
"balance_after": "1048.00",
"description": "Pagamento cobrança #42",
"reference_type": "App\\Models\\Charge",
"reference_id": 42,
"created_at": "2026-06-05T14:30:00.000000Z"
}
],
"last_page": 1,
"total": 1
}
Tipos de Transação
| Tipo |
Descrição |
Sinal |
charge_received |
Recebimento de cobrança |
+ |
split_received |
Recebimento via split |
+ |
charge_fee |
Taxa da cobrança |
- |
withdrawal |
Saque |
- |
withdrawal_fee |
Taxa do saque |
- |
refund |
Estorno |
+ |
adjustment |
Ajuste manual |
+/- |
Exemplo
curl -X GET "https://app.flowinpay.com.br/api/v1/transactions?type=charge_received&start_date=2026-06-01" \
-H "X-Api-Key: fpk_xxxx" \
-H "Accept: application/json"
Consultar Transação
GET /api/v1/transactions/{id}
Response (200)
{
"transaction": {
"id": 150,
"type": "charge_received",
"amount": "48.00",
"balance_before": "1000.00",
"balance_after": "1048.00",
"description": "Pagamento cobrança #42",
"reference_type": "App\\Models\\Charge",
"reference_id": 42,
"created_at": "2026-06-05T14:30:00.000000Z"
}
}
Exemplo
curl -X GET "https://app.flowinpay.com.br/api/v1/transactions/150" \
-H "X-Api-Key: fpk_xxxx" \
-H "Accept: application/json"
Webhooks
Webhooks
Webhooks permitem que seu sistema receba notificações em tempo real sobre eventos na FlowinPay.
TODOS OS WEBHOOKS TEM TIMEOUT DE 5 SEGUNDOS.
Registrar Webhook
POST /api/v1/webhooks
Body
| Campo |
Tipo |
Obrigatório |
Descrição |
url |
string |
Sim |
URL de destino (HTTPS recomendado) |
events |
array |
Sim |
Eventos desejados |
description |
string |
Não |
Descrição do webhook |
Eventos Disponíveis
| Evento |
Descrição |
charge.created |
Cobrança criada |
charge.completed |
Cobrança paga |
charge.expired |
Cobrança expirada |
charge.cancelled |
Cobrança cancelada |
charge.refunded |
Cobrança estornada |
withdrawal.completed |
Saque processado |
withdrawal.failed |
Saque falhou |
dispute.opened |
Contestação aberta |
dispute.accepted |
Contestação aceita |
dispute.rejected |
Contestação rejeitada |
dispute.cancelled |
Contestação cancelada |
Exemplo
curl -X POST "https://app.flowinpay.com.br/api/v1/webhooks" \
-H "X-Api-Key: fpk_xxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://seusite.com/webhook/flowinpay",
"events": ["charge.completed", "charge.expired"]
}'
Response (201)
{
"message": "Webhook criado com sucesso! Guarde o secret — não será exibido novamente.",
"webhook": {
"id": 1,
"url": "https://seusite.com/webhook/flowinpay",
"events": ["charge.completed", "charge.expired"],
"is_active": true,
"secret": "whsec_xxxxxxxxxxxxxxxxxxxx",
"created_at": "2026-06-05T15:00:00Z"
}
}
Importante: O secret é usado para verificar a assinatura dos webhooks. Guarde-o — não será exibido novamente.
Listar Webhooks
GET /api/v1/webhooks
Response (200)
[
{
"id": 1,
"url": "https://seusite.com/webhook/flowinpay",
"events": ["charge.completed", "charge.expired"],
"is_active": true,
"secret_preview": "whsec_xxxxx••••••••",
"last_triggered_at": "2026-06-05T16:00:00Z",
"failure_count": 0,
"created_at": "2026-06-05T15:00:00Z"
}
]
Atualizar Webhook
Disponível apenas no painel (autenticação Bearer Token). Não disponível via API Key.
Remover Webhook
DELETE /api/v1/webhooks/{id}
Response (200)
{ "message": "Webhook removido com sucesso" }
Regenerar Secret
Disponível apenas no painel (autenticação Bearer Token). Não disponível via API Key.
Testar Webhook
Disponível apenas no painel (autenticação Bearer Token). Não disponível via API Key.
Payload do Webhook
Quando um evento ocorre, a FlowinPay envia um POST para sua URL:
Headers
| Header |
Descrição |
event |
Tipo do evento (ex: charge.completed) |
X-FlowinPay-Event |
Tipo do evento (duplicado) |
X-FlowinPay-Signature |
Assinatura HMAC-SHA256 |
Content-Type |
application/json |
Payload — charge.created / charge.completed / charge.expired / charge.cancelled
{
"event": "charge.completed",
"charge": {
"id": 42,
"correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"value": 50.00,
"fee_value": 2.00,
"net_value": 48.00,
"status": "paid",
"description": "Pedido #123",
"paid_at": "2026-06-05T14:30:00Z",
"created_at": "2026-06-05T13:00:00Z"
},
"timestamp": "2026-06-05T14:30:00Z"
}
Payload — withdrawal.completed
{
"event": "withdrawal.completed",
"withdrawal": {
"id": 15,
"value": "100.00",
"fee_value": "2.00",
"net_value": "98.00",
"pix_key": "12345678901",
"pix_key_type": "cpf",
"status": "completed",
"processed_at": "2026-06-05T15:30:00Z",
"created_at": "2026-06-05T15:00:00Z"
},
"timestamp": "2026-06-05T15:30:00Z"
}
Payload — withdrawal.failed
{
"event": "withdrawal.failed",
"withdrawal": {
"id": 15,
"value": "100.00",
"fee_value": "2.00",
"net_value": "98.00",
"pix_key": "12345678901",
"pix_key_type": "cpf",
"status": "failed",
"description": "Chave PIX inválida",
"created_at": "2026-06-05T15:00:00Z"
},
"timestamp": "2026-06-05T15:05:00Z"
}
Payload — dispute.opened / dispute.accepted / dispute.rejected / dispute.cancelled
{
"event": "dispute.opened",
"dispute": {
"id": 3,
"charge_id": 42,
"external_id": "dsp_woovi_abc123",
"type": "chargeback",
"status": "open",
"amount": "50.00",
"reason": "Cliente não reconheceu a compra",
"due_at": "2026-06-20T00:00:00Z",
"created_at": "2026-06-10T10:00:00Z"
},
"timestamp": "2026-06-10T10:00:00Z"
}
Response esperada
Retorne HTTP 200 com JSON vazio {}.
Verificar Assinatura
Use o secret do webhook para verificar que o payload é legítimo:
PHP
$secret = 'whsec_xxxxxxxxxxxx';
$signature = $_SERVER['HTTP_X_FLOWINPAY_SIGNATURE'] ?? '';
$payload = file_get_contents('php://input');
$expected = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Assinatura inválida');
}
// Processar evento
$data = json_decode($payload, true);
Node.js
const crypto = require('crypto');
app.post('/webhook/flowinpay', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-flowinpay-signature'];
const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid');
}
const event = JSON.parse(req.body);
// Processar evento
res.json({});
});
Retry
Em caso de falha (timeout ou status != 2xx), a FlowinPay reenvia:
| Tentativa |
Intervalo |
| 1ª |
Imediato |
| 2ª |
5 segundos |
| 3ª |
30 segundos |
| 4ª |
2 minutos |
| 5ª |
15 minutos |
Após 5 tentativas, o webhook é marcado como falho.
Webhook Automático via callbackUrl
Ao criar uma cobrança via API com callbackUrl, a FlowinPay cria automaticamente um webhook na sua conta. Não é necessário criar webhook manualmente — basta passar a URL no momento da cobrança.
curl -X POST "https://app.flowinpay.com.br/api/v1/charges" \
-H "X-Api-Key: fpk_xxxx" \
-H "Content-Type: application/json" \
-d '{
"value": 50.00,
"acquirer_id": 1,
"callbackUrl": "https://seusite.com/webhook/flowinpay"
}'
O webhook será criado com todos os eventos ativos e aparecerá na aba Integração → Webhooks do painel.
Guia de Webhooks
Guia: Como Receber Webhooks da FlowinPay
Visão Geral
Quando um evento ocorre na FlowinPay (ex: pagamento confirmado), enviamos um POST para a URL que você configurou. Seu sistema precisa:
- Receber o POST na sua URL
- Retornar HTTP 200 rapidamente (timeout de 5 segundos)
- Verificar a assinatura (opcional, mas recomendado)
- Processar o evento
Passo 1: Configurar a URL
Você pode configurar a URL de webhook de duas formas:
Opção A: Via Painel
- Acesse Integração → Webhooks
- Clique em Novo Webhook
- Preencha a URL e selecione os eventos
Opção B: Via API (Automático)
Ao criar uma cobrança, passe webhook_url no body:
curl -X POST "https://app.flowinpay.com.br/api/v1/charges" \
-H "X-Api-Key: fpk_xxxx" \
-H "Content-Type: application/json" \
-d '{
"value": 50.00,
"acquirer_id": 1,
"webhook_url": "https://seusite.com/webhook/flowinpay"
}'
A FlowinPay criará automaticamente um webhook na sua conta.
Passo 2: Criar o Endpoint
Seu endpoint precisa:
- Aceitar POST
- Ler o body como JSON
- Retornar HTTP 200 em menos de 5 segundos
PHP
<?php
// webhook.php
$secret = 'whsec_seu_secret_aqui';
// Ler o payload
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
// Verificar assinatura (recomendado)
$signature = $_SERVER['HTTP_X_FLOWINPAY_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Assinatura inválida');
}
// Retornar 200 IMEDIATAMENTE
http_response_code(200);
header('Content-Type: application/json');
echo '{}';
// Processar o evento (depois de retornar 200)
$event = $data['event'] ?? '';
$charge = $data['charge'] ?? [];
switch ($event) {
case 'charge.completed':
// Pagamento confirmado!
$correlationId = $charge['correlation_id'];
$value = $charge['value'];
// Atualizar pedido no banco de dados
break;
case 'charge.expired':
// Cobrança expirada
break;
case 'charge.cancelled':
// Cobrança cancelada
break;
}
Node.js (Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
const SECRET = 'whsec_seu_secret_aqui';
// IMPORTANTE: usar raw body para verificar assinatura
app.post('/webhook/flowinpay', express.raw({ type: 'application/json' }), (req, res) => {
// Retornar 200 IMEDIATAMENTE
res.json({});
// Verificar assinatura
const signature = req.headers['x-flowinpay-signature'];
const expected = crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
console.error('Assinatura inválida');
return;
}
// Processar evento
const event = JSON.parse(req.body);
switch (event.event) {
case 'charge.completed':
console.log('Pagamento confirmado!', event.charge.correlation_id);
// Atualizar pedido no banco de dados
break;
case 'charge.expired':
console.log('Cobrança expirada');
break;
case 'charge.cancelled':
console.log('Cobrança cancelada');
break;
}
});
app.listen(3000, () => console.log('Webhook rodando na porta 3000'));
Python (Flask)
from flask import Flask, request, jsonify
import hmac
import hashlib
app = Flask(__name__)
SECRET = 'whsec_seu_secret_aqui'
@app.route('/webhook/flowinpay', methods=['POST'])
def webhook():
# Retornar 200 IMEDIATAMENTE
response = jsonify({})
# Verificar assinatura
payload = request.get_data()
signature = request.headers.get('X-FlowinPay-Signature', '')
expected = hmac.new(SECRET.encode(), payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
print('Assinatura inválida')
return response
# Processar evento
data = request.get_json()
event = data.get('event')
charge = data.get('charge', {})
if event == 'charge.completed':
print(f'Pagamento confirmado! {charge.get("correlation_id")}')
# Atualizar pedido no banco de dados
elif event == 'charge.expired':
print('Cobrança expirada')
elif event == 'charge.cancelled':
print('Cobrança cancelada')
return response
if __name__ == '__main__':
app.run(port=3000)
Java (Spring Boot)
@RestController
public class FlowinPayWebhookController {
private static final String SECRET = "whsec_seu_secret_aqui";
@PostMapping("/webhook/flowinpay")
public ResponseEntity<String> handleWebhook(@RequestBody String payload,
@RequestHeader("X-FlowinPay-Signature") String signature) {
// Verificar assinatura
String expected = hmacSha256(payload, SECRET);
if (!MessageDigest.isEqual(expected.getBytes(), signature.getBytes())) {
return ResponseEntity.status(401).body("Assinatura inválida");
}
// Retornar 200 IMEDIATAMENTE
ResponseEntity<String> response = ResponseEntity.ok("{}");
// Processar evento
JSONObject data = new JSONObject(payload);
String event = data.getString("event");
JSONObject charge = data.getJSONObject("charge");
switch (event) {
case "charge.completed":
System.out.println("Pagamento confirmado! " + charge.getString("correlation_id"));
break;
case "charge.expired":
System.out.println("Cobrança expirada");
break;
case "charge.cancelled":
System.out.println("Cobrança cancelada");
break;
}
return response;
}
private String hmacSha256(String data, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
byte[] hash = mac.doFinal(data.getBytes());
return bytesToHex(hash);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
)
const secret = "whsec_seu_secret_aqui"
type WebhookPayload struct {
Event string `json:"event"`
Charge struct {
ID int `json:"id"`
CorrelationID string `json:"correlation_id"`
Value float64 `json:"value"`
Status string `json:"status"`
} `json:"charge"`
Timestamp string `json:"timestamp"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Retornar 200 IMEDIATAMENTE
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte("{}"))
// Ler payload
body, _ := io.ReadAll(r.Body)
// Verificar assinatura
signature := r.Header.Get("X-FlowinPay-Signature")
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(signature), []byte(expected)) {
fmt.Println("Assinatura inválida")
return
}
// Processar evento
var payload WebhookPayload
json.Unmarshal(body, &payload)
switch payload.Event {
case "charge.completed":
fmt.Printf("Pagamento confirmado! %s\n", payload.Charge.CorrelationID)
case "charge.expired":
fmt.Println("Cobrança expirada")
case "charge.cancelled":
fmt.Println("Cobrança cancelada")
}
}
func main() {
http.HandleFunc("/webhook/flowinpay", webhookHandler)
http.ListenAndServe(":3000", nil)
}
C# (.NET)
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
[ApiController]
public class FlowinPayWebhookController : ControllerBase
{
private const string Secret = "whsec_seu_secret_aqui";
[HttpPost("/webhook/flowinpay")]
public async Task<IActionResult> HandleWebhook()
{
// Retornar 200 IMEDIATAMENTE
var response = Ok(new { });
// Ler payload
using var reader = new StreamReader(Request.Body);
var payload = await reader.ReadToEndAsync();
// Verificar assinatura
var signature = Request.Headers["X-FlowinPay-Signature"].ToString();
var expected = HmacSha256(payload, Secret);
if (!CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(signature),
Encoding.UTF8.GetBytes(expected)))
{
return Unauthorized("Assinatura inválida");
}
// Processar evento
var data = JsonSerializer.Deserialize<WebhookPayload>(payload);
switch (data?.Event)
{
case "charge.completed":
Console.WriteLine($"Pagamento confirmado! {data.Charge.CorrelationId}");
break;
case "charge.expired":
Console.WriteLine("Cobrança expirada");
break;
case "charge.cancelled":
Console.WriteLine("Cobrança cancelada");
break;
}
return response;
}
private static string HmacSha256(string data, string secret)
{
var keyBytes = Encoding.UTF8.GetBytes(secret);
var dataBytes = Encoding.UTF8.GetBytes(data);
using var hmac = new HMACSHA256(keyBytes);
var hash = hmac.ComputeHash(dataBytes);
return Convert.ToHexString(hash).ToLower();
}
}
Ruby (Sinatra)
require 'sinatra'
require 'json'
require 'openssl'
SECRET = 'whsec_seu_secret_aqui'
post '/webhook/flowinpay' do
# Retornar 200 IMEDIATAMENTE
status 200
content_type :json
'{}'
# Verificar assinatura
payload = request.body.read
signature = request.env['HTTP_X_FLOWINPAY_SIGNATURE']
expected = OpenSSL::HMAC.hexdigest('sha256', SECRET, payload)
unless Rack::Utils.secure_compare(signature, expected)
halt 401, 'Assinatura inválida'
end
# Processar evento
data = JSON.parse(payload)
event = data['event']
charge = data['charge'] || {}
case event
when 'charge.completed'
puts "Pagamento confirmado! #{charge['correlation_id']}"
when 'charge.expired'
puts 'Cobrança expirada'
when 'charge.cancelled'
puts 'Cobrança cancelada'
end
end
Passo 3: Testar
Teste via Painel
- Vá em Integração → Webhooks
- Clique no webhook
- Clique em Testar
Teste via API
curl -X POST "https://app.flowinpay.com.br/api/v1/webhooks/1/test" \
-H "X-Api-Key: fpk_xxxx"
Teste Local (ngrok)
# Instalar ngrok
ngrok http 3000
# Use a URL gerada como webhook_url
# Ex: https://abc123.ngrok.io/webhook/flowinpay
Passo 4: Processar o Evento
Eventos Importantes
| Evento |
Quando |
Ação recomendada |
charge.created |
Cobrança criada |
Salvar no banco, exibir QR Code |
charge.completed |
Pagamento confirmado |
Liberar acesso, enviar produto |
charge.expired |
Cobrança expirada |
Cancelar pedido |
charge.cancelled |
Cobrança cancelada |
Cancelar pedido |
charge.refunded |
Estorno processado |
Estornar pedido |
Exemplo: Atualizar Pedido
<?php
// Depois de retornar 200
if ($event === 'charge.completed') {
$correlationId = $charge['correlation_id'];
$value = $charge['value'];
// Atualizar pedido no banco
$pdo->prepare("UPDATE orders SET status = 'paid', paid_at = NOW() WHERE payment_id = ?")
->execute([$correlationId]);
// Liberar acesso
// Enviar produto
// Enviar email de confirmação
}
Boas Práticas
- Retorne 200 rápido — Processe o evento de forma assíncrona (filas, jobs)
- Verifique a assinatura — Sempre valide o header
X-FlowinPay-Signature
- Implemente idempotência — O mesmo evento pode ser enviado mais de uma vez
- Use HTTPS — URLs HTTP são aceitas mas não recomendadas
- Log de eventos — Registre todos os webhooks recebidos para debugging
Troubleshooting
Webhook não está recebendo
- Verifique se a URL está correta e acessível
- Verifique se o servidor está rodando
- Teste com ngrok para ambientes locais
Assinatura inválida
- Verifique se o secret está correto
- Verifique se está usando o body raw (não parseado)
Timeout
- Retorne 200 antes de processar o evento
- Use filas/jobs para processamento pesado
Evento duplicado
- Implemente idempotência usando o
correlation_id
- Verifique se o pedido já foi processado antes de atualizar
Taxas
Taxas
Consultar Taxas Ativas
GET /api/v1/fees/current
Endpoint público — não requer autenticação.
Response (200)
{
"fees": {
"percentual": "2.00",
"fixed_value": "1.00",
"withdrawal_fee": "2.00",
"minimum_charge": "2.00",
"maximum_charge": "150.00",
"minimum_withdrawal": "10.00",
"maximum_withdrawal": "1000.00"
}
}
Campos
| Campo |
Tipo |
Descrição |
percentual |
string |
Taxa percentual sobre cobranças (%) |
fixed_value |
string |
Taxa fixa por cobrança (R$) |
withdrawal_fee |
string |
Taxa fixa por saque (R$) |
minimum_charge |
string |
Valor mínimo de cobrança (R$) |
maximum_charge |
string |
Valor máximo de cobrança (R$) |
minimum_withdrawal |
string |
Valor mínimo de saque (R$) |
maximum_withdrawal |
string |
Valor máximo de saque (R$) |
Exemplo
curl -X GET "https://app.flowinpay.com.br/api/v1/fees/current" \
-H "Accept: application/json"
Página de Pagamento
Página Pública de Pagamento
Visão Geral
Ao criar uma cobrança, a FlowinPay gera um payment_link_url que pode ser compartilhado diretamente com o pagador. Essa página pública exibe o QR Code e o PIX copia-e-cola para pagamento.
GET /pay/{correlationId}
Essa é uma página HTML/SPA — não uma rota de API. O integrador apenas compartilha o link.
Fluxo
- Integrador cria cobrança via
POST /api/v1/charges
- Recebe
payment_link_url no response
- Compartilha o link com o pagador (WhatsApp, email, botão de pagamento, etc.)
- Pagador acessa o link → vê QR Code + PIX copia-e-cola + countdown de expiração
- Pagador paga → webhook
charge.completed é disparado automaticamente
O que o pagador vê
- Valor da cobrança
- QR Code do PIX
- Código PIX copia-e-cola (
br_code)
- Countdown de expiração (24h)
- Status do pagamento (aguardando → confirmado)
Endpoint Público da API
A página consulta o status da cobrança via endpoint público (sem autenticação):
GET /api/public/charge/{correlationId}
Response (200)
{
"charge": {
"id": 42,
"correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"value": "50.00",
"status": "active",
"br_code": "00020101021226580014br.gov.bcb.pix...",
"qr_code_image": "https://app.flowinpay.com.br/storage/qr/abc123.png",
"expires_at": "2026-06-06T13:00:00Z"
}
}
Rate limit: 30 requisições por minuto por IP.
Campos retornados (públicos)
| Campo |
Descrição |
value |
Valor da cobrança |
status |
Status atual (active, paid, expired) |
br_code |
PIX copia-e-cola |
qr_code_image |
URL da imagem do QR Code |
expires_at |
Data de expiração |
Nota: Dados sensíveis (customer, API key, etc.) não são expostos neste endpoint.
Erros
Erros
A API FlowinPay utiliza códigos de status HTTP padrão e retorna erros em formato JSON.
Formato do Erro
{
"message": "Descrição do erro",
"errors": {
"campo": ["Mensagem de validação"]
}
}
Códigos de Status
| Status |
Descrição |
200 |
Sucesso |
201 |
Criado com sucesso |
400 |
Requisição inválida |
401 |
Não autenticado |
403 |
Sem permissão |
404 |
Recurso não encontrado |
422 |
Erro de validação |
429 |
Rate limit excedido |
500 |
Erro interno do servidor |
Erros Comuns
401 - Não Autenticado
{
"message": "Unauthenticated."
}
Causas:
- App ID ausente no header
X-Api-Key
- App ID inválida ou revogada
- Formato incorreto (use
X-Api-Key: fpk_xxxx)
Solução:
# Verifique se a App ID está correta e ativa no painel
curl -H "X-Api-Key: fpk_xxxx" "https://app.flowinpay.com.br/api/v1/balance"
403 - Sem Permissão
{
"message": "Esta ação não é autorizada."
}
Causas:
- API Key sem permissão para o endpoint
- Usuário sem acesso ao recurso
Solução:
- Verifique as permissões da API Key
- Use uma chave com permissões adequadas
404 - Não Encontrado
{
"message": "No query results for model [App\\Models\\Charge] 999"
}
Causas:
- ID do recurso não existe
- Recurso pertence a outro usuário
Solução:
- Verifique se o ID está correto
- Consulte a listagem para IDs válidos
422 - Erro de Validação
{
"message": "O valor da cobrança é obrigatório.",
"errors": {
"value": ["O valor da cobrança é obrigatório."]
}
}
Erros de Validação Comuns:
Cobranças
| Campo |
Erro |
Solução |
value |
O valor da cobrança é obrigatório |
Envie o campo value |
value |
O valor mínimo é R$ 2,00 |
Aumente o valor |
value |
O valor máximo é R$ 150,00 |
Diminua o valor |
acquirer_id |
Selecione uma adquirente |
Envie acquirer_id: 1 |
acquirer_id |
Adquirente inválida |
Use um ID válido |
customer_email |
E-mail do cliente inválido |
Formato: user@example.com |
Saques
| Campo |
Erro |
Solução |
value |
Saldo insuficiente |
Verifique o saldo |
value |
Valor mínimo R$10 |
Aumente o valor |
pix_key |
Chave PIX obrigatória |
Envie a chave PIX |
pix_key_type |
Tipo inválido |
Use: cpf, cnpj, email, phone, random |
Webhooks
| Campo |
Erro |
Solução |
url |
URL inválida |
Use formato completo: https://... |
events |
Evento inválido |
Use eventos da lista disponível |
events |
Selecione pelo menos um |
Envie array com 1+ eventos |
429 - Rate Limit
{
"message": "Too Many Attempts.",
"retry_after": 45
}
Limites:
| Endpoint |
Limite |
| API v1 (todas rotas) |
60 req/min |
| Login/Registro |
10 req/min |
| Cobranças (painel) |
30 req/min |
| Página pública de pagamento |
30 req/min |
| Webhook receiver (adquirente) |
100 req/min |
Solução:
- Aguarde
retry_after segundos
- Implemente exponential backoff
- Use webhooks em vez de polling
500 - Erro Interno
{
"message": "Erro interno do servidor"
}
Causas:
- Erro na adquirente (Woovi)
- Timeout na API externa
- Falha temporária
Solução:
- Tente novamente em alguns segundos
- Verifique o status da API:
GET /api/health
- Se persistir, contate o suporte
Erros Específicos da Adquirente
Erro ao criar cobrança
{
"message": "Erro ao criar cobrança na adquirente"
}
Causas comuns:
- Chave PIX inválida
- Valor fora do limite
- Falha de conexão com Woovi
Solução:
- Verifique os dados da cobrança
- Tente com outro valor
- Aguarde e tente novamente
Boas Práticas
- Sempre verifique o status — Não assuma sucesso
- Trate erros de validação — Use o campo
errors para feedback
- Implemente retry — Para erros 429 e 500
- Log de erros — Registre erros para debugging
- Idempotência — Use
correlation_id para evitar duplicatas
Exemplo de Tratamento de Erros
JavaScript
async function createCharge(data) {
try {
const response = await fetch('/api/v1/charges', {
method: 'POST',
headers: {
'X-Api-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
if (response.status === 422) {
// Erro de validação
console.error('Validação:', error.errors);
throw new Error(Object.values(error.errors).flat().join(', '));
}
if (response.status === 429) {
// Rate limit - aguardar
await new Promise(r => setTimeout(r, error.retry_after * 1000));
return createCharge(data); // Retry
}
throw new Error(error.message);
}
return await response.json();
} catch (error) {
console.error('Erro:', error.message);
throw error;
}
}
PHP
function createCharge(array $data): array {
$ch = curl_init('https://app.flowinpay.com.br/api/v1/charges');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'X-Api-Key: ' . env('FLOWINPAY_API_KEY'),
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode >= 400) {
$errorMessage = $result['message'] ?? 'Erro desconhecido';
if ($httpCode === 422) {
$errors = collect($result['errors'])->flatten()->implode(', ');
throw new Exception("Erro de validação: {$errors}");
}
if ($httpCode === 429) {
sleep($result['retry_after']);
return createCharge($data); // Retry
}
throw new Exception($errorMessage);
}
return $result;
}
Central de ajuda · FlowinPay · Criar conta