Introdução #
A API da SafiPay é HTTP com JSON. Com ela o seu sistema gera cobranças Pix com QR Code, acompanha pagamentos, consulta o saldo, envia transferências Pix e saca em cripto para uma carteira.
https://www.safipaybr.com
Convenções
- Envie o corpo em JSON com o header
Content-Type: application/json. - Valores são em reais, como número com ponto decimal:
150.00é R$ 150,00. Nas respostas os números vêm sem zeros à direita (150,2.5). - Datas seguem ISO 8601 em UTC, como
2026-09-12T17:03:21.000Z. - Sucesso responde
{ "success": true, "data": { … } }. Erro responde{ "error": "mensagem" }com o status HTTP correspondente. Alguns erros trazem tambémcode, comoIP_NOT_ALLOWEDouAMOUNT_BELOW_MINIMUM, para você tratar no código sem depender do texto. - IDs de transação começam com
sfp_(cobranças) ousfp_out_(transferências).
Mudanças recentes
Estas regras passaram a valer para todas as integrações. Confira se o seu sistema já atende a cada uma:
- Credenciais só em header.
ciecsna URL são recusados com400eCREDENTIALS_IN_QUERY. Veja Autenticação. - Só chave de API em
/api/gateway/*. A sessão do painel não vale nessas rotas: semciecs, a resposta é401comAPI_KEY_REQUIRED. Idempotency-Keyrecomendada empix-createepix-out: repetir a mesma chave devolve a resposta original, sem criar nada de novo. Com caractere fora do formato,400comIDEMPOTENCY_KEY_INVALID. Veja Idempotência.- Valores com até 2 casas decimais.
amountprecisa ser número com ponto decimal."100abc", vírgula e 3 casas são recusados comINVALID_AMOUNT. - Copia e cola precisa ter valor. Em
pix-out, código sem valor definido é recusado (BRCODE_DYNAMIC_NOT_ALLOWED), e o valor do código precisa ser igual aamount(BRCODE_AMOUNT_MISMATCH). Veja Transferência Pix. - Códigos de erro estáveis. Trate os erros pelo campo
code. A lista completa está em Códigos de erro. - Taxa conforme a verificação da conta, com valor mínimo de cobrança por conta (
AMOUNT_BELOW_MINIMUM). Veja Taxas. - Saque cripto pela API.
crypto-withdrawenvia USDT (rede BEP20) para uma carteira a partir do saldo em reais, com cotação e taxas emcrypto-quote. Veja Saque cripto.
Autenticação #
Toda requisição leva as credenciais de uma chave de API em dois headers:
ck_.cs_.Os nomes X-Client-Id e X-Client-Secret também são aceitos. Crie e revogue chaves em API Keys. O Client Secret aparece uma única vez, na criação, porque a SafiPay guarda só um hash dele. Uma chave revogada para de funcionar na hora.
- Só em header. Credenciais na URL, como
?ci=…&cs=…, são recusadas com400e"code": "CREDENTIALS_IN_QUERY", sem executar a operação. Endereços ficam gravados em logs e proxies: se o Client Secret já apareceu numa URL, troque a chave. - Só chave de API. Os endpoints
/api/gateway/*não aceitam a sessão do painel. Semciecs, a resposta é401com"code": "API_KEY_REQUIRED", mesmo com login ativo no navegador.
Os exemplos desta página leem as credenciais das variáveis de ambiente SAFIPAY_CLIENT_ID e SAFIPAY_CLIENT_SECRET. Para testar a sua chave, consulte o saldo:
IPs permitidos
Cada chave pode ter uma lista de IPs permitidos, em API Keys (Configurar › IPs permitidos). Com a lista vazia, que é o padrão, a chave aceita chamadas de qualquer IP. Com pelo menos um item, só os IPs da lista conseguem usá-la.
- Aceita IPv4 (
177.71.200.14), IPv6 (2804:14c:5b73::1) e faixas CIDR (189.40.88.0/24,2804:14c:5b73:8a10::/64), até 50 itens por chave. - Vale o IP público de onde a chamada chega à SafiPay. Se o seu servidor sai para a internet por proxy, NAT ou balanceador, libere o IP de saída, não o IP interno.
- A regra vale para todos os endpoints chamados com a chave. O painel e o checkout não passam por ela.
Uma chamada de IP fora da lista é recusada com 403 e o código IP_NOT_ALLOWED, sem gerar cobrança nem mexer no saldo. As credenciais são conferidas antes do IP: com ci ou cs errados, a resposta continua 401. A mensagem traz o IP que a SafiPay recebeu (IPv4 aparece como IPv4, mesmo vindo por IPv6), o que ajuda a descobrir qual liberar:
Erros de autenticação
| Status | Mensagem | Quando acontece |
|---|---|---|
| 400 | Credenciais não são aceitas na URL. Envie ci e cs só nos headers e troque o client secret se ele já apareceu em alguma URL. | ci ou cs enviados na query string. O corpo traz "code": "CREDENTIALS_IN_QUERY". |
| 401 | Credenciais inválidas. | O par ci/cs não confere, a chave foi revogada ou a conta não está ativa. |
| 401 | Esta rota exige chave de API: envie os headers ci e cs. A sessão do painel não é aceita aqui. | Faltou enviar ci ou cs. O corpo traz "code": "API_KEY_REQUIRED". |
| 403 | IP não autorizado (203.0.113.50). | A chave tem lista de IPs permitidos e a chamada veio de outro IP. O corpo traz "code": "IP_NOT_ALLOWED". |
Idempotência #
Conexões caem. Para você repetir uma requisição sem gerar cobrança ou transferência em dobro, pix-create, pix-out e crypto-withdraw são idempotentes quando recebem uma chave por operação.
:, _ e -: qualquer outro caractere, inclusive espaço e ponto, devolve 400 com IDEMPOTENCY_KEY_INVALID. Também aceito como X-Idempotency-Key. Opcional, mas recomendado em pix-create e principalmente em pix-out: protege contra cobrança ou transferência em dobro quando a sua requisição é repetida depois de um timeout.Como funciona
| Situação | Resultado |
|---|---|
| Sem o header | A operação roda normalmente, mas sem proteção contra repetição: nada fica guardado e, se você reenviar a mesma requisição, mesmo com o corpo idêntico, uma nova cobrança ou transferência é criada. |
| Chave fora do formato | 400 "code": "IDEMPOTENCY_KEY_INVALID". Nada é criado nem debitado. |
| Primeira requisição com a chave | Executa e guarda o status e o corpo da resposta. Erros em que nada foi criado nem debitado, como dados inválidos, não ficam guardados. |
| Mesma chave e mesmo corpo | Devolve a resposta guardada, sem executar de novo. |
| Mesma chave, corpo diferente | 409 Idempotency-Key reutilizada com body diferente. |
| Mesma chave enquanto a primeira ainda roda | 409 Requisição idêntica em processamento. Tente novamente em instantes. |
| Chave de uma transferência usada em outra | 409 "code": "IDEMPOTENCY_KEY_ALREADY_USED". Consulte a transferência original antes de repetir. |
| Registro de idempotência indisponível | 503 "code": "IDEMPOTENCY_UNAVAILABLE". Nada foi executado: repita em instantes com a mesma chave. |
As chaves valem por conta e por endpoint durante 48 horas, contadas da primeira requisição. Depois disso a chave é ignorada e repetir cria uma operação nova; em pix-out e crypto-withdraw, a chave de uma transferência já feita continua recusada com 409 e IDEMPOTENCY_KEY_ALREADY_USED. O corpo é comparado como JSON serializado: mesmos campos, mesmos valores e na mesma ordem.
Repetir com segurança
- Não recebeu resposta (timeout, queda de rede): repita com a mesma chave e o mesmo corpo. Se a primeira chegou a ser processada, você recebe a resposta original.
- Recebeu um erro e corrigiu os dados: o corpo mudou, então use uma chave nova.
- Quer tentar de novo o mesmo pedido depois de um erro (por exemplo,
502): use uma chave nova, porque a resposta de erro também fica guardada.
Taxas #
A taxa depende da verificação da conta. Enquanto a conta não é verificada, vale a taxa padrão de conta não verificada. Depois da aprovação em Verificação, passam a valer as taxas da sua conta. A regra é a mesma para cobranças pela API, checkout hospedado, cobranças do painel e transferências.
| Conta | Pix recebido | Transferência |
|---|---|---|
| Não verificada | 10% + R$ 1,00 | R$ 2,00 |
| Verificada | Taxa da sua conta | Taxa da sua conta |
| Cobrança acima de R$ 1.000,00 (qualquer conta) | 10% + R$ 1,00 | — |
O saque cripto tem taxa própria, igual para qualquer conta: veja Saque cripto › Taxas.
O que usar no seu sistema
feeenetAmountda resposta são a fonte da verdade. Grave os valores devolvidos porpix-create,pix-status,pix-oute pelo webhook. Não recalcule a taxa: ela muda quando a conta é verificada.- A taxa de cada operação é definida quando ela é criada. Uma cobrança gerada antes da aprovação mantém a taxa com que foi criada, mesmo que seja paga depois.
- Cobranças acima de R$ 1.000,00 (até R$ 5.000,00) pagam 10% + R$ 1,00, com a conta verificada ou não. Até R$ 1.000,00 vale a taxa da conta.
- Cada conta tem um valor mínimo de cobrança: o menor valor que deixa pelo menos R$ 0,01 depois da taxa, nunca abaixo de R$ 1,00. Com a taxa de conta não verificada, o mínimo é R$ 1,13.
Valor abaixo do mínimo
Uma cobrança abaixo do mínimo da conta é recusada com 400 e "code": "AMOUNT_BELOW_MINIMUM". O campo minAmount traz o mínimo atual, em reais. Nada é criado.
Criar cobrança Pix #
/api/gateway/pix-create
Gera uma cobrança Pix com QR Code e código copia e cola. A cobrança nasce com status pending e expira em 30 minutos (veja expiresAt).
Corpo
5000.00, como número com ponto decimal e no máximo 2 casas (150.00). "100abc", vírgula (150,00) e 3 casas são recusados com INVALID_AMOUNT. O mínimo é o da conta, a partir de 1.00, e depende da taxa: veja Taxas.Campos de texto passam por limpeza antes de gravar: tags HTML são removidas e o que passar do limite é cortado.
Taxa
A taxa sai do valor recebido e depende da verificação da conta (veja Taxas). A conta é fee = amount × percentual ÷ 100, arredondado ao centavo, + valor fixo, e netAmount = amount − fee. Use sempre fee e netAmount da resposta: são os valores que valem para a cobrança.
pending na criação.data:image/png;base64,. Pode vir null: nesse caso, gere o QR Code a partir de copyPaste.null; nesse caso, mostre o QR Code e o copia e cola.Erros
| Status | Mensagem | Quando acontece |
|---|---|---|
| 400 | Valor inválido. Envie amount em reais, com até 2 casas decimais (ex.: 150.00). | amount ausente, com texto, vírgula ou mais de 2 casas. "code": "INVALID_AMOUNT". |
| 400 | Valor mínimo para esta conta: R$ 1,13. | amount abaixo do mínimo da conta. O corpo traz "code": "AMOUNT_BELOW_MINIMUM" e minAmount. Veja Taxas. |
| 400 | Valor máximo R$ 5000,00 por cobrança. | amount acima de 5000. |
| 400 | CPF/CNPJ do pagador inválido. | payerDocument sem 11 ou 14 dígitos ou com dígito verificador errado. |
| 400 | Mensagem de idempotência | A Idempotency-Key tem caracteres fora do formato (IDEMPOTENCY_KEY_INVALID). |
| 429 | Mensagem de limite de requisições | Muitas cobranças seguidas. "code": "RATE_LIMITED"; espere os segundos do header Retry-After. |
| 409 | Mensagens de idempotência | Chave reutilizada com outro corpo ou requisição idêntica em andamento. |
| 502 | Não foi possível gerar a cobrança agora. Tente novamente em instantes. | O Pix não foi gerado. Tente de novo com uma nova Idempotency-Key. |
| 503 | Serviço de pagamento temporariamente indisponível. Tente novamente em instantes. | "code": "ACQUIRER_UNAVAILABLE". Nada foi criado; tente de novo em instantes. |
| 500 | Erro ao registrar transação. | Falha ao gravar a cobrança. Tente de novo com uma nova chave. |
Consultar cobrança #
/api/gateway/pix-status?transactionId={id}
Devolve o status atual da cobrança. Se ela ainda estiver pending, a SafiPay confere o pagamento antes de responder. Se o pagamento já tiver entrado, o saldo é creditado e o webhook pix.paid é disparado nesse momento. A conferência tem um limite por conta (algumas por minuto, no máximo uma a cada 5 segundos por cobrança): acima dele a resposta traz o último status conhecido, sem erro. Até 120 consultas por minuto por conta; acima disso, 429 com RATE_LIMITED e Retry-After. Para acompanhar pagamentos, prefira o webhook.
Parâmetros de consulta
sfp_…).null, inclusive em cobranças que tinham a imagem na criação. Nesse caso, gere o QR Code a partir de copyPaste, que sempre vem enquanto a cobrança está pendente.null na mesma resposta em que o status acabou de mudar para paid; aparece nas consultas seguintes e no webhook.O endpoint também aceita o ID de uma transferência (sfp_out_…). Nesse caso o status vem do registro da transferência na SafiPay. Num saque cripto, a resposta traz também "type": "pix_out", "method": "crypto" e o objeto crypto (moeda, rede, endereço mascarado e estimativa).
Erros
| Status | Mensagem | Quando acontece |
|---|---|---|
| 400 | transactionId obrigatório. | Faltou o parâmetro transactionId. |
| 404 | Transação não encontrada. | O ID não existe ou é de outra conta. |
Saldo #
/api/gateway/balance
Saldo da conta e totais de movimentação, em reais. balance é o que está disponível agora: já desconta as taxas e o que está reservado por transferências em andamento, porque valor e taxa da transferência saem do saldo no momento do pedido.
Parâmetros de consulta
AAAA-MM-DD (meia-noite no horário de Brasília) ou data e hora em ISO 8601.from.Sem from e to, os totais cobrem todo o histórico da conta; com eles, só o período. Para setembro inteiro, use from=2026-09-01 e to=2026-10-01. balance e blockedBalance não dependem do período: são sempre o valor do momento. Data em formato inválido responde 400.
balance e ignora from/to.BRL.updatedAt.balance e currency continuam iguais aos de antes. Integrações que só leem esses dois campos não precisam mudar nada.
Transferência Pix #
/api/gateway/pix-out
Envia um Pix a partir do seu saldo, para uma chave Pix ou pagando um código copia e cola.
"mode": "key"Informe pixKey e pixKeyType.
"mode": "brcode"Informe o código completo em brCode, com o valor preenchido.
Sem mode, a SafiPay usa brcode quando o corpo tem brCode e key nos outros casos.
Corpo
1.00. Obrigatório também no modo copia e cola.key ou brcode.CPF, CNPJ, EMAIL, TELEFONE ou CHAVE_ALEATORIA. Maiúsculas ou minúsculas.amount. Também aceito como copyPaste ou pixCopyPaste, desde que mode seja brcode.Tipos de chave
| pixKeyType | O que aceita | Enviada como |
|---|---|---|
CPF | 11 dígitos, com ou sem pontuação. Dígitos verificadores conferidos. | 123.456.789-09 |
CNPJ | 14 dígitos, com ou sem pontuação. Dígitos verificadores conferidos. | 12.345.678/0001-95 |
EMAIL | Endereço de e-mail válido. | Em minúsculas |
TELEFONE | DDD + número (10 ou 11 dígitos), com ou sem +55. | +5511987654321 |
CHAVE_ALEATORIA | UUID: 32 caracteres hexadecimais, com ou sem hífens. | 6f1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d |
No modo copia e cola, o código precisa começar com 0002 e terminar com o CRC (6304 + 4 caracteres), que é conferido. Quebras de linha e caracteres invisíveis colados junto são removidos.
amount, centavo a centavo. Código sem valor definido, como o de cobranças em que quem paga digita o valor, é recusado com 400 e BRCODE_DYNAMIC_NOT_ALLOWED. Valor diferente de amount é recusado com BRCODE_AMOUNT_MISMATCH, que traz o valor do código em brCodeAmount. Nos dois casos nada é debitado.Taxa, saldo e limites
- Cada transferência tem uma taxa fixa, que depende da verificação da conta (veja Taxas). O saldo precisa cobrir valor + taxa, e os dois saem do saldo no momento do pedido.
feeetotalDebitedda resposta são os valores debitados. - A sua conta pode ter limite por transferência e limite diário (veja em Configurações). O limite diário soma o valor das transferências pendentes e concluídas desde 00:00, no horário de Brasília. A taxa e as transferências que falharam não entram na conta.
- Até 10 transferências por minuto e 60 por hora pela API, e no máximo 20 pedidos de transferência por hora por conta, somando painel, API e saque cripto. Acima disso,
429comRATE_LIMITEDe o headerRetry-After.
Respostas
O pedido foi aceito para envio. O status fica pending e o resultado final chega pelo webhook pixout.paid ou pixout.failed. Se falhar depois, valor e taxa voltam ao saldo.
O envio não teve confirmação conclusiva (instabilidade ou tempo esgotado), então ainda não dá para saber se o Pix saiu. O valor continua debitado e a transferência fica pending até a confirmação. Não repita com outra Idempotency-Key: isso pode pagar duas vezes.
O envio foi recusado (por exemplo, chave Pix inexistente). O corpo traz "code": "PIX_OUT_REFUSED" e a mensagem em error. A transferência é marcada como failed e valor + taxa voltam ao saldo na hora.
Erros
| Status | Mensagem | Quando acontece |
|---|---|---|
| 400 | Valor mínimo R$ 1,00. | amount menor que 1. |
| 400 | Valor inválido. Envie amount em reais, com até 2 casas decimais (ex.: 150.00). | amount ausente, com texto, vírgula ou mais de 2 casas. "code": "INVALID_AMOUNT". |
| 400 | Chave PIX obrigatória. | Modo key sem pixKey. |
| 400 | Tipo de chave PIX inválido. | pixKeyType ausente ou fora da lista. |
| 400 | CPF inválido. CNPJ inválido. Email inválido. Telefone inválido. Use DDD + número. Chave aleatória inválida (UUID). | A chave não bate com o tipo informado. |
| 400 | Código PIX copia-e-cola inválido (CRC ou formato). | Modo brcode com código incompleto ou CRC errado. "code": "BRCODE_INVALID". |
| 400 | Este código Pix não tem valor definido. Peça ao recebedor um código com valor ou use a chave Pix. | "code": "BRCODE_DYNAMIC_NOT_ALLOWED". |
| 400 | O valor informado (R$ 50,00) é diferente do valor do código Pix (R$ 89,90). | "code": "BRCODE_AMOUNT_MISMATCH", com brCodeAmount. |
| 400 | Valor acima do limite por saque (R$ 1000.00). | Passou do limite por transferência da conta. |
| 400 | Limite diário de saques atingido (usado R$ 1350.40 de R$ 20000.00). | A soma do dia passaria do limite diário. |
| 400 | Saldo insuficiente. Disponível: R$ 80.00 | O saldo não cobre valor + taxa. |
| 400 | Limite diário de transferências para conta não verificada atingido (usado R$ 500,00 de R$ 500,00). Conclua a verificação em /verificacao. | Limite diário da conta não verificada. "code": "UNVERIFIED_DAILY_LIMIT", com used, limit e verificationUrl. |
| 400 | Não foi possível reservar saldo. | A reserva foi recusada por outro motivo. |
| 403 | Transferências bloqueadas para revisão de segurança da conta. Fale com o suporte. | "code": "WITHDRAWALS_BLOCKED". |
| 403 | Saldo negativo por devolução de Pix recebido. Transferências ficam bloqueadas até a revisão da conta. Fale com o suporte. | "code": "NEGATIVE_BALANCE". |
| 400 | Não foi possível concluir a transferência. Confira os dados do Pix e tente novamente. Valor e taxa já voltaram ao saldo. | Envio recusado. "code": "PIX_OUT_REFUSED". Valor e taxa já voltaram ao saldo. |
| 400 | Mensagem de idempotência | A Idempotency-Key tem caracteres fora do formato (IDEMPOTENCY_KEY_INVALID). Nada é debitado. |
| 409 | Esta Idempotency-Key já foi usada em outra transferência. Consulte o status dela antes de repetir. | "code": "IDEMPOTENCY_KEY_ALREADY_USED". |
| 429 | Mensagem de limite de requisições | Muitas transferências seguidas, ou o limite de 20 por hora da conta (painel, API e cripto somados). "code": "RATE_LIMITED"; espere os segundos do header Retry-After. |
| 503 | Serviço de pagamento temporariamente indisponível. Nada foi debitado; tente novamente em instantes. | "code": "ACQUIRER_UNAVAILABLE". Consulte o Extrato antes de repetir. |
| 409 | Mensagens de idempotência | Chave reutilizada com outro corpo ou requisição idêntica em andamento. |
| 500 | Falha ao reservar saldo. | Erro ao reservar. Nada foi debitado; tente de novo com uma nova chave. |
Os valores nas mensagens de limite e saldo são exemplos: a mensagem real traz os números da sua conta.
Saque cripto #
/api/gateway/crypto-withdraw
/api/gateway/crypto-quote?amount={valor}&network=BEP20
Saca do seu saldo em cripto, direto para uma carteira. Você informa o valor em reais: o saldo é debitado em reais, o valor é convertido para USDT e enviado na rede escolhida. As mesmas regras valem para o saque cripto feito no painel, em Transferir.
Moedas e redes
| coin | network | Endereço |
|---|---|---|
USDT | BEP20 (BNB Smart Chain) | 0x seguido de 40 caracteres hexadecimais (42 no total), como 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed. |
- Hoje a única opção é USDT na rede BEP20. Outra rede (TRC20, ERC20, Polygon) ou outra moeda é recusada com
400eCRYPTO_NETWORK_INVALID. - Endereço com letras maiúsculas e minúsculas misturadas precisa conferir com o checksum do endereço (EIP-55); um caractere trocado é recusado com
CRYPTO_ADDRESS_INVALID. Endereço todo em minúsculas também é aceito. - Espaços, quebras de linha e caracteres invisíveis colados junto com o endereço são removidos. O endereço
0x000…000é recusado.
Corpo
24.00 a 3000.00, dentro do limite por transferência da sua conta. É o valor convertido; a taxa SafiPay é debitada à parte.BEP20.USDT. Se enviado, precisa ser a moeda da rede.Envie Idempotency-Key como em pix-out (veja Idempotência): repetir a mesma chave e o mesmo corpo devolve a resposta original sem enviar de novo.
Taxas
- Taxa SafiPay: R$ 2,00 por saque cripto, debitada do saldo junto com o valor. Vem em
fee. A taxa de transferência Pix não é cobrada no saque cripto. - Taxa de conversão: 5% sobre o valor convertido, paga de dentro do valor da transferência.
- Taxa da rede: R$ 3,00, paga de dentro do valor da transferência.
- Convertido = valor − taxa de conversão − taxa da rede. Como os 5% incidem sobre o convertido, e não sobre o valor inteiro, a taxa de conversão de uma transferência de R$ 100,00 é R$ 4,62, não R$ 5,00. Vem em
convertedAmount. - Total debitado = valor + taxa SafiPay. Vem em
totalDebited. - Valor estimado recebido = convertido ÷ cotação, arredondado para baixo, em USDT. É uma estimativa: o valor final usa a cotação do momento do processamento.
| Exemplo com R$ 100,00 e cotação de R$ 5,25 por USDT | Valor |
|---|---|
| Valor da transferência | R$ 100,00 |
| Taxa SafiPay | + R$ 2,00 |
| Total debitado do saldo | R$ 102,00 |
| Taxa de conversão (5% de R$ 92,38) | − R$ 4,62 |
| Taxa da rede | − R$ 3,00 |
| Convertido | R$ 92,38 |
| Valor estimado recebido | ≈ 17,59 USDT |
As taxas da conversão e da rede podem mudar. Consulte crypto-quote antes de enviar e grave fee, totalDebited e crypto da resposta em vez de recalcular.
Saldo e limites
- O saldo precisa cobrir valor + taxa SafiPay; os dois saem do saldo no momento do pedido.
- Valor de R$ 24,00 a R$ 3.000,00 por saque, e até o limite por transferência da sua conta (padrão R$ 1.000,00, veja em Configurações).
- O limite diário da conta vale para Pix e cripto juntos: soma o valor das transferências pendentes e concluídas desde 00:00, no horário de Brasília.
- Até 3 saques cripto por minuto e 20 por hora por conta, somando todas as chaves, dentro do limite de 20 pedidos de transferência por hora da conta (Pix e cripto, painel e API). Acima disso,
429comRATE_LIMITEDe o headerRetry-After. - Com as transferências pausadas pela SafiPay, o pedido é recusado com
503eWITHDRAWALS_PAUSED, sem debitar.
Respostas
O pedido foi aceito. O status fica pending enquanto a conversão e o envio na rede acontecem, o que costuma levar alguns minutos. O resultado final chega pelo webhook pixout.paid ou pixout.failed. Se falhar, valor e taxa voltam ao saldo.
O envio não teve confirmação conclusiva, então ainda não dá para saber se saiu. O valor continua debitado e o saque fica pending até a confirmação. Não repita com outra Idempotency-Key: um saque cripto enviado duas vezes não pode ser desfeito.
O envio foi recusado antes de sair. O corpo traz "code": "CRYPTO_OUT_REFUSED", o saque é marcado como failed e valor + taxa voltam ao saldo na hora.
crypto.address vem sempre mascarado nas respostas, na consulta e no webhook. crypto.estimatedAmount e crypto.rate podem vir null quando não há cotação disponível no momento do pedido.
Cotação e taxas
GET /api/gateway/crypto-quote devolve as redes aceitas, os limites, as taxas e a estimativa para um valor, calculada pela SafiPay. Não debita nada. Até 30 consultas por minuto por conta.
estimate vem null.BEP20 (padrão).maxAmount já considera o limite por transferência da sua conta. Sem cotação no momento, rate, quotedAt e estimate.estimatedAmount vêm null: a conversão usa a cotação do momento do processamento.
Status e webhook
O saque cripto é uma transferência ("type": "pix_out", ID sfp_out_…): segue os status de transferência, aparece no Extrato e é consultado em pix-status. Os webhooks são os mesmos da transferência Pix, pixout.paid e pixout.failed, com data.method igual a crypto e o objeto data.crypto.
Erros
| Status | Mensagem | Quando acontece |
|---|---|---|
| 400 | Valor inválido. Envie amount em reais, com até 2 casas decimais (ex.: 150.00). | amount ausente, com texto, vírgula ou mais de 2 casas. "code": "INVALID_AMOUNT". |
| 400 | Valor mínimo para transferência cripto: R$ 24,00. | "code": "CRYPTO_AMOUNT_BELOW_MINIMUM", com minAmount. |
| 400 | Valor máximo por transferência cripto: R$ 3.000,00. | "code": "CRYPTO_AMOUNT_ABOVE_MAXIMUM", com maxAmount. |
| 400 | Moeda ou rede inválida. Hoje a transferência cripto aceita USDT na rede BEP20. | network ausente ou fora da lista, ou coin diferente da moeda da rede. "code": "CRYPTO_NETWORK_INVALID". |
| 400 | Endereço inválido para a rede BEP20. Ele começa com 0x e tem 42 caracteres. Endereço com letras maiúsculas e minúsculas trocadas. Copie o endereço de novo direto da carteira. Informe o endereço da carteira que vai receber. | Endereço fora do formato da rede, com checksum errado ou ausente. "code": "CRYPTO_ADDRESS_INVALID". |
| 400 | Valor acima do limite por saque (R$ 1000.00). | Passou do limite por transferência da conta. |
| 400 | Limite diário de saques atingido (usado R$ 1350.40 de R$ 20000.00). | A soma do dia (Pix e cripto) passaria do limite diário. |
| 400 | Saldo insuficiente. Disponível: R$ 80.00 | O saldo não cobre valor + taxa SafiPay. |
| 400 | Limite diário de transferências para conta não verificada atingido (usado R$ 500,00 de R$ 500,00). Conclua a verificação em /verificacao. | "code": "UNVERIFIED_DAILY_LIMIT". |
| 400 | Não foi possível concluir a transferência agora. Nada foi enviado e o valor com a taxa já voltou ao saldo. | Envio recusado. "code": "CRYPTO_OUT_REFUSED". Valor e taxa já voltaram ao saldo. |
| 400 | Mensagem de idempotência | Idempotency-Key fora do formato (IDEMPOTENCY_KEY_INVALID) ou obrigatória e ausente (IDEMPOTENCY_KEY_REQUIRED). Nada é debitado. |
| 401 403 | Mensagens de autenticação | Credenciais ausentes ou inválidas, ou IP fora da lista da chave. |
| 403 | Transferências bloqueadas para revisão de segurança da conta. Fale com o suporte. | "code": "WITHDRAWALS_BLOCKED" (ou NEGATIVE_BALANCE). |
| 409 | Esta Idempotency-Key já foi usada em outra transferência. Consulte o status dela antes de repetir. | "code": "IDEMPOTENCY_KEY_ALREADY_USED". |
| 409 | Mensagens de idempotência | Chave reutilizada com outro corpo ou requisição idêntica em andamento. |
| 429 | Muitas transferências cripto seguidas nesta conta. Tente novamente em 42s. Limite de transferências cripto por hora atingido. Tente novamente em 1800s. | Mais de 3 saques cripto por minuto, 20 por hora, ou o limite de 20 transferências por hora da conta. "code": "RATE_LIMITED"; espere os segundos do header Retry-After. |
| 500 | Falha ao reservar saldo. | Erro ao reservar. Confira o Extrato antes de repetir com uma nova chave. |
| 503 | Saque cripto indisponível no momento. Nada foi debitado; tente novamente mais tarde. | "code": "CRYPTO_WITHDRAWAL_UNAVAILABLE". Vale também para crypto-quote. |
| 503 | Transferências temporariamente indisponíveis. Nada foi debitado; tente novamente em instantes. | "code": "WITHDRAWALS_PAUSED". |
| 503 | Serviço de pagamento temporariamente indisponível. Nada foi debitado; tente novamente em instantes. | "code": "ACQUIRER_UNAVAILABLE". Consulte o Extrato antes de repetir. |
Os valores nas mensagens de limite e saldo são exemplos: a mensagem real traz os números da sua conta.
Webhooks #
Com uma URL cadastrada, a SafiPay faz um POST no seu servidor quando uma transação muda de status. A URL da conta fica em Webhooks. Cada chave de API pode ter URL e segredo próprios, em API Keys.
Configuração
- A URL precisa ser HTTPS, num domínio público e sem usuário e senha. URLs que apontam ou resolvem para localhost, rede privada ou endereços reservados são recusadas ao salvar e conferidas de novo a cada entrega. A regra vale para a URL da conta e para a de cada chave.
- Trocar ou remover uma URL, da conta ou de uma chave, exige a senha da conta.
- O segredo da conta, com 64 caracteres hexadecimais, fica em Webhooks. O segredo de uma chave começa com
whsec_e é gerado quando você cadastra a primeira URL da chave; você vê e troca em API Keys, confirmando com a senha.
Conta ou chave: para onde vai o aviso
| Transação | URL | Segredo |
|---|---|---|
| Criada pela API com uma chave que tem URL própria | Da chave | Da chave |
| Criada com uma chave sem URL própria, pelo painel ou pelo checkout | Da conta | Da conta |
- Vale para cobranças e transferências, no envio automático, nos reenvios automático e manual e na rotina de conciliação.
- Com uma URL por chave, cada sistema recebe só os avisos das transações criadas com a chave dele. Valide a assinatura com o segredo da mesma chave.
- Transações criadas com uma chave que depois foi revogada continuam avisando a URL dessa chave, com o segredo dela.
- O teste de uma chave, em API Keys, usa a URL e o segredo da chave. Sem URL própria, vai para o webhook da conta.
Eventos
| Evento | Quando | Envio |
|---|---|---|
pix.paid | Cobrança paga. O valor líquido já entrou no saldo. | Sempre |
pixout.paid | Transferência concluída (Pix ou saque cripto). | Sempre |
pixout.failed | Transferência falhou depois de aceita. Valor e taxa voltaram ao saldo. | Sempre |
pix.expired | Cobrança expirou sem pagamento. | Opcional |
pix.failed | A cobrança falhou ou foi cancelada. | Opcional |
test.ping | Botão de teste em Webhooks ou em API Keys. | Teste |
pix.expired e pix.failed são enviados quando a opção Enviar cobranças expiradas e falhas está ligada em Webhooks. Contas criadas antes dessa opção começam com ela desligada, para não surpreender integrações que tratam qualquer aviso como pagamento; contas novas começam com ela ligada. Desligada, esses eventos só chegam quando você reenvia manualmente o webhook de uma cobrança nesse status.
Uma cobrança expirada ainda pode ser paga, se o pagamento for confirmado depois. Nesse caso chegam os dois avisos, primeiro pix.expired e depois pix.paid: não cancele um pedido de forma definitiva só pelo pix.expired.
event igual a pix.paid. Os eventos opcionais chegam no mesmo formato, com o status real e paidAt: null.Corpo
sfp_… ou sfp_out_…).pix_in (cobrança) ou pix_out (transferência).netAmount é igual a amount.null em transferências.null em pix.expired e pix.failed.true, em reenvios manuais.method igual a crypto e crypto com coin, network, address (mascarado), estimatedAmount, rate, conversionFee e networkFee.No test.ping, data.clientId indica a chave testada, quando o teste é feito por uma chave em API Keys. O corpo chega compacto, sem quebras de linha; os exemplos estão formatados só para leitura.
Headers
| Header | Valor |
|---|---|
X-SafiPay-Signature-V2 | t=<unix>,v1=<hex>. v1 é o HMAC-SHA256 de t, um ponto e o corpo bruto, em hexadecimal minúsculo. É a assinatura recomendada. |
X-SafiPay-Timestamp | O mesmo t da V2: momento do envio, em segundos Unix. |
X-SafiPay-Signature | Legado: HMAC-SHA256 só do corpo bruto, em hexadecimal minúsculo. Continua sendo enviado. |
X-SafiPay-Event | Nome do evento, igual a event. |
X-SafiPay-Delivery | ID único (UUID) desta tentativa. Muda a cada reenvio. |
Content-Type | application/json |
User-Agent | SafiPay/1.0 |
As duas assinaturas usam o mesmo segredo: o da chave, quando o aviso vai para a URL da chave, ou o da conta. Cada tentativa, inclusive os reenvios, tem um t novo e por isso uma assinatura V2 nova; o corpo é sempre o mesmo.
Resposta esperada e reenvio
- Responda com qualquer status 2xx em até 10 segundos. O corpo da sua resposta é ignorado.
- Redirecionamentos (3xx) não são seguidos e contam como falha. Cadastre a URL final.
- Outros status, erro de conexão, certificado inválido ou tempo esgotado contam como falha.
- Eventos automáticos que falham entram na fila de reenvio: até 6 tentativas no total, com intervalos mínimos de 1 min, 5 min, 30 min, 2 h e 12 h. A fila é processada por uma rotina agendada, então o reenvio pode demorar mais que o intervalo.
- O reenvio usa o mesmo corpo da primeira tentativa, com assinatura e
tnovos. Se a transação mudou de status desde a falha, o evento antigo não é reenviado. Quando uma tentativa é entregue, as pendentes do mesmo evento são canceladas. - O teste e o reenvio manual fazem uma tentativa só.
Todas as tentativas, com status HTTP, duração e próxima tentativa, aparecem no histórico da página Webhooks.
Validar a assinatura
Valide a V2 em todo aviso, antes de converter o corpo para objeto:
- Leia
X-SafiPay-Signature-V2e separetev1. - Recuse se
testiver a mais de 300 segundos do seu relógio, para mais ou para menos. Assim um aviso copiado não pode ser reenviado depois. - Calcule o HMAC-SHA256 de
t, um ponto e os bytes do corpo exatamente como chegaram, com o segredo certo: o da chave ou o da conta. - Compare o resultado em hexadecimal com
v1numa comparação de tempo constante. Recuse o que não bater.
X-SafiPay-Signature continua sendo enviado, com o HMAC só do corpo, e quem usa esse header não precisa mudar nada. Para migrar, valide a V2 junto com a antiga e, quando estiver funcionando, remova a validação antiga.transactionId + event. Não use X-SafiPay-Delivery para isso: ele muda a cada tentativa.Link de pagamento #
Toda cobrança criada pela API ganha uma página pública de pagamento. O endereço vem em paymentLink, na resposta de pix-create e de pix-status:
https://www.safipaybr.com/pix/{token}
Use quando não quiser montar a tela de pagamento: mande o link por WhatsApp ou e-mail, ou redirecione o cliente para ele. Se o seu site já mostra o QR Code, não precisa usar.
O que o cliente vê
Nome da sua conta, descrição e valor, com QR Code, copia e cola e contador até expiresAt. O status atualiza sozinho.
Confirmação com valor, descrição e horário, assim que o pagamento entra.
Aviso de que a cobrança não pode mais ser paga, com orientação para pedir um novo link a você.
- A página não gera outra cobrança. Para cobrar de novo depois que expirar, crie outra com
pix-createe envie o novo link. - Ela não mostra dados do pagador, taxa nem IDs internos. O token tem 32 caracteres hexadecimais aleatórios e só abre esta página.
- Pagar pela página é o mesmo que pagar o QR Code: a cobrança aparece no Extrato e dispara
pix.paidno webhook, como qualquer outra. paymentLinkpode virnullse o recurso ainda não estiver ativo na sua conta. Nesse caso, mostre o QR Code e o copia e cola.
Checkout hospedado #
Para vender sem integrar a API, crie um produto em Produtos. Cada produto ganha um link público de pagamento:
https://checkout.seguro.safipaybr.com/payment/{código}
- O comprador abre o link, informa nome, e-mail e, conforme a configuração do produto, CPF/CNPJ e telefone. Paga com Pix e vê a confirmação na mesma página.
- O preço vai do mínimo da conta até R$ 5.000,00, com a mesma taxa de uma cobrança pela API (veja Taxas).
- Cada pedido gera uma cobrança Pix comum: aparece no Extrato com o nome do produto na descrição e dispara
pix.paidno seu webhook quando é pago, no mesmo formato da API. - Você escolhe o tema da página, a mensagem de confirmação e, se quiser, um link HTTPS para onde levar o comprador depois do pagamento.
Status das transações #
| Status | Cobrança (pix_in) | Transferência (pix_out) |
|---|---|---|
| pending | Aguardando pagamento. O QR Code vale até expiresAt. | Pedido feito, aguardando confirmação. Valor e taxa já saíram do saldo. |
| paid | Pagamento confirmado e líquido creditado. Dispara pix.paid. | Transferência concluída. Dispara pixout.paid. |
| expired | Não foi paga no prazo. Dispara pix.expired se a opção estiver ligada. Se o pagamento for confirmado depois, ainda muda para paid e credita normalmente. | — |
| failed | A cobrança falhou ou foi cancelada. Dispara pix.failed se a opção estiver ligada. | Recusada ou falhou. Valor e taxa voltam ao saldo. Dispara pixout.failed quando a falha chega depois do pedido. |
| refunded | Pix devolvido ao pagador (estorno ou MED). | — |
Cobranças sem pagamento confirmado 1 hora depois de criadas, com expiresAt já vencido, passam a expired automaticamente, sem esperar a rotina de conciliação: uma consulta a pix-status nesse ponto já responde expired. Se o pagamento for confirmado depois, a cobrança muda para paid e credita normalmente. Por isso, trate pix.paid mesmo para pedidos que você já deu como vencidos.
Erros #
Erros vêm com o status HTTP e o corpo { "error": "mensagem" }. As mensagens são em português e podem ser mostradas ao seu time. Para decidir no código, use o status HTTP. As mensagens de cada endpoint estão na seção dele.
| Status | Significado | O que fazer |
|---|---|---|
| 200 · 201 | Sucesso. | — |
| 202 | Transferência com envio ambíguo (pix-out ou crypto-withdraw). | Aguarde o webhook. Não repita com outra chave. |
| 400 | Dados inválidos, limite, saldo insuficiente, transferência recusada (PIX_OUT_REFUSED), credenciais na URL ou valor abaixo do mínimo da conta (AMOUNT_BELOW_MINIMUM). | Corrija e envie de novo com uma nova Idempotency-Key. |
| 401 | Credenciais ausentes (API_KEY_REQUIRED) ou inválidas, chave revogada ou conta inativa. | Envie ci e cs em header. Confira os valores em API Keys. |
| 403 | IP fora da lista de IPs permitidos da chave. "code": "IP_NOT_ALLOWED". | Libere o IP que aparece na mensagem em API Keys, ou chame de um IP da lista. |
| 404 | Transação não encontrada. ou Endpoint não encontrado. | Confira o ID e a URL. |
| 405 | Método não permitido. | Use o método indicado no endpoint. |
| 409 | Conflito de idempotência. | Espere e repita; se o corpo mudou, use outra chave. |
| 429 | Muitas requisições (RATE_LIMITED). | Espere os segundos do header Retry-After. |
| 500 | Erro interno. ou falha ao gravar. | Tente de novo em instantes com uma nova chave. Em transferências, confira antes no Extrato se ela não foi criada. |
| 502 | A cobrança não foi gerada. | Tente de novo com uma nova chave. |
| 503 | Serviço de pagamento ou registro de idempotência indisponível. | Tente de novo em instantes. Veja o code. |
Códigos de erro
Quando existe, o campo code identifica o erro sem depender do texto da mensagem. Trate por ele.
| Status | code | Quando acontece | O que fazer |
|---|---|---|---|
| 400 | INVALID_AMOUNT | amount não é número com ponto decimal e até 2 casas: "100abc", vírgula ou 3 casas. | Envie 150.00. Corrija e repita com uma nova Idempotency-Key. |
| 400 | AMOUNT_BELOW_MINIMUM | Cobrança abaixo do mínimo da conta. Traz minAmount. | Cobre pelo menos minAmount. Veja Taxas. |
| 400 | IDEMPOTENCY_KEY_INVALID | A chave tem caractere fora de letras, números, :, _ e -, ou passa de 128. | Gere a chave só com esses caracteres (um UUID serve). |
| 409 | IDEMPOTENCY_KEY_ALREADY_USED | A chave já foi usada em outra transferência. | Consulte a transferência original. Para uma operação nova, use outra chave. |
| 503 | IDEMPOTENCY_UNAVAILABLE | Não deu para garantir execução única agora. Nada foi executado. | Tente de novo em instantes com a mesma chave e o mesmo corpo. |
| 400 | CREDENTIALS_IN_QUERY | ci ou cs na URL. | Envie só em header e troque a chave se o secret já apareceu numa URL. |
| 401 | API_KEY_REQUIRED | Rota de API chamada sem ci e cs (a sessão do painel não vale). | Envie as credenciais da chave em header. |
| 403 | IP_NOT_ALLOWED | IP fora da lista de IPs permitidos da chave. | Libere o IP da mensagem em API Keys. |
| 429 | RATE_LIMITED | Muitas requisições seguidas. Vem com o header Retry-After (segundos). | Espere o Retry-After e repita. Não repita em laço. |
| 400 | BRCODE_INVALID | Copia e cola incompleto ou com CRC errado. | Copie o código inteiro de novo. |
| 400 | BRCODE_AMOUNT_MISMATCH | amount diferente do valor do código. Traz brCodeAmount. | Envie amount igual a brCodeAmount. |
| 400 | BRCODE_DYNAMIC_NOT_ALLOWED | Código sem valor definido. | Peça um código com valor ou transfira pela chave Pix. |
| 403 | WITHDRAWALS_BLOCKED | Transferências bloqueadas para revisão de segurança da conta. | Fale com o suporte. Não repita. |
| 403 | NEGATIVE_BALANCE | Saldo negativo por devolução de Pix recebido; transferências bloqueadas. | Fale com o suporte para regularizar. |
| 400 | UNVERIFIED_DAILY_LIMIT | Limite diário de transferências da conta não verificada. Traz used, limit e verificationUrl. | Espere o próximo dia ou conclua a verificação. |
| 400 | PIX_OUT_REFUSED | Transferência recusada no envio (ex.: chave Pix inexistente). Valor e taxa já voltaram ao saldo. | Confira a chave ou o código copia e cola e envie de novo com uma nova Idempotency-Key. |
| 503 | ACQUIRER_UNAVAILABLE | Serviço de pagamento indisponível no momento. | Tente de novo em instantes. Em transferências, confira antes o Extrato. |
| 503 | WITHDRAWALS_PAUSED | Transferências (Pix e cripto) pausadas pela SafiPay. Nada foi debitado. | Tente de novo mais tarde. |
| 400 | CRYPTO_NETWORK_INVALID | Saque cripto com rede ou moeda fora da lista. | Envie network BEP20 (USDT). |
| 400 | CRYPTO_ADDRESS_INVALID | Endereço da carteira fora do formato da rede ou com checksum errado. | Copie o endereço de novo direto da carteira. Nada foi debitado. |
| 400 | CRYPTO_AMOUNT_BELOW_MINIMUM CRYPTO_AMOUNT_ABOVE_MAXIMUM | Saque cripto fora da faixa de R$ 24,00 a R$ 3.000,00. Traz minAmount ou maxAmount. | Ajuste amount. |
| 400 | CRYPTO_OUT_REFUSED | Saque cripto recusado no envio. Valor e taxa já voltaram ao saldo. | Confira rede e endereço e envie de novo com uma nova Idempotency-Key. |
| 503 | CRYPTO_WITHDRAWAL_UNAVAILABLE | Saque cripto indisponível no momento. Nada foi debitado. | Tente de novo mais tarde ou use pix-out. |
Boas práticas #
Salve junto do pedido assim que a cobrança for criada. É com ele que você consulta o status e reconhece o webhook.
Recomendada em pix-create, pix-out e crypto-withdraw. Use o ID do seu pedido ou um UUID guardado com ele. Em timeout, repita com a mesma chave e o mesmo corpo para não duplicar.
Valide a assinatura V2 e o horário de todo aviso. Antes de liberar algo valioso, confirme com pix-status.
Se o aviso não chegou alguns minutos depois do pagamento, consulte pix-status. A consulta confere o pagamento (dentro do limite de conferências da conta) e dispara o webhook se ele tiver entrado.
Devolva 2xx em até 10 s e faça o trabalho pesado depois. Ignore eventos repetidos pelo par transactionId + event.
Perdeu um aviso? No painel, reenvie o webhook de uma transação paga ou com falha pelo Extrato e acompanhe em Webhooks.
Guarde em variáveis de ambiente do servidor e limite cada chave aos IPs dele. Para trocar uma chave, crie a nova, publique e só então revogue a antiga.
No seu sistema, guarde valores em centavos inteiros ou tipo decimal, nunca em ponto flutuante. Grave fee e netAmount da resposta em vez de recalcular a taxa.
Pronto para integrar? Crie uma chave e faça a primeira cobrança.