DOCS
Dashboard Criar conta
API v1 · Base URL: https://n7pay.com/api/v1

Documentação da API N7 Pay

O N7 Pay é uma plataforma global de orquestração de pagamentos. Esta API REST permite criar links de pagamento, gerenciar clientes, consultar transações e receber notificações em tempo real via webhooks.

Todos os endpoints retornam JSON. Datas seguem ISO 8601 (UTC). Valores monetários são strings decimais (ex: "299.90") para evitar erros de ponto flutuante.

Gateways suportados: Asaas (PIX, boleto, cartão em BRL) e Stripe (cartão internacional em USD, EUR e BRL). O roteamento é automático via Smart Routing.

Autenticação

Toda requisição deve incluir o header Authorization com sua chave de API:

Authorization: Bearer n7_sk_sand_SuaChaveAqui

Idempotency-Key

Envie o header Idempotency-Key em POST /charges e POST /charges/international para que uma requisição repetida devolva a mesma cobrança em vez de criar outra. É opcional, e sem ele o comportamento é o de sempre.

Idempotency-Key: 8f14e45f-ea0c-4b2e-9f5a-1c3d7b920a11

Use um valor por tentativa de pagamento, estável entre recargas da página: se o pagador recarregar enquanto a cobrança está sendo criada, é isso que impede a segunda cobrança. Gere um novo valor só quando for de fato um novo pedido.

Situação Resposta
Chave nova A cobrança é criada normalmente
Mesma chave, mesmo corpo, já concluída A resposta original, idêntica, com o header idempotency-replayed: true
Mesma chave, primeira ainda em andamento 409 request_in_progress. Aguarde e consulte o resultado; não crie outra cobrança
Mesma chave, corpo diferente 422 idempotency_key_reused. Gere uma chave nova

Uma cobrança que falha devolve a chave: você pode repetir a mesma requisição com a mesma chave. A chave vale por 24 horas e é escopada por empresa e por ambiente, então o mesmo valor pode ser usado uma vez em sandbox e uma vez em produção.

Em POST /fx/quotes o mesmo header tem outro comportamento. Lá a chave não guarda uma resposta: ela fica presa à cotação, e sem janela de 24 horas. Reusar a chave depois que a cotação venceu devolve a mesma cotação vencida, não abre outra: vem com expires_in: 0 e, por herança do registro, status: "active", que nesse caso não quer dizer utilizável. Cobrar com ela falha em 422 quote_expired. Use uma chave nova a cada tentativa de cotação, por exemplo versionando o valor. A cotação é um preço com validade curta, e repetir uma chave ali pede de volta um preço que já passou.
Endpoint A chave guarda Validade Reuso depois
POST /charges e /charges/international A resposta inteira 24 horas Repete a resposta com idempotency-replayed: true. Passadas as 24h, o mesmo valor pode ser usado de novo
POST /fx/quotes A cotação emitida Sem prazo Devolve sempre aquela cotação, mesmo vencida. Confira expires_in antes de cobrar
Prefixo Ambiente Uso
n7_sk_sand_… Sandbox Secreta, no seu servidor. Testes: nenhuma cobrança real
n7_sk_live_… Live Secreta, no seu servidor. Produção: cobranças reais
n7_pk_sand_… e n7_pk_live_… Os dois Publicável, pode ir no navegador. Só serve para POST /tokens; em qualquer outro endpoint a resposta é 403 publishable_key_not_allowed

A base URL é a mesma nos dois ambientes. Quem decide o ambiente é a chave, e tudo o que ela cria fica naquele ambiente: um cliente, uma cobrança ou um token criados com a chave sandbox não existem para a chave live, e vice-versa. Consultar com a chave de um ambiente o id criado no outro responde 404.

Segurança: Nunca exponha chaves live no frontend ou em repositórios públicos. Cada chave é exibida uma única vez no momento da criação.
Não existe token de sessão. Toda chamada leva a chave de API no header Authorization. Versões anteriores desta página descreviam um JWT emitido em POST /api/v1/auth/token, mas nenhum endpoint o aceita: use a chave secreta no servidor e a publicável no navegador.

Ambiente Sandbox

O sandbox usa a infraestrutura de testes dos gateways. Nenhuma cobrança real é processada, e o formato de toda resposta e de todo webhook é o mesmo de produção.

Como ativar: gere no painel, com o ambiente em Sandbox, uma chave com prefixo n7_sk_sand_. Toda chamada com ela vai para o sandbox do gateway, mesmo na base URL de produção. Os endpoints de webhook também são por ambiente: cadastre no painel, com o ambiente em Sandbox, o endereço que vai receber os eventos de teste.
Comportamento Sandbox Live
Cobranças reais Não Sim
Webhooks Entregues aos endpoints sandbox, no mesmo formato e com a mesma assinatura Entregues aos endpoints live
Pix pago Pelo simulador Quando o pagador paga
Rate limit 100 req/min 1000 req/min
Dados Só do sandbox Só de produção

Cartões de teste

No sandbox o resultado depende do número do cartão. Verificado contra o sandbox do Asaas em outubro de 2026, pelo mesmo caminho da API da N7:

Cartão Resultado
4444 4444 4444 4444, ou qualquer número válido, validade futura, CVV 123 Aprovado. O token é emitido e a cobrança volta 201 com status: "paid"
5184 0197 4037 3151 (Mastercard) Recusado. POST /tokens responde 422 tokenization_failed e POST /charges com o cartão inline responde 422 charge_declined, os dois com invalid_action e "Transação não autorizada" em details
4916 5613 5824 0741 (Visa) Recusado, igual ao anterior

Simulando pago, recusado e contestado

No sandbox ninguém paga um Pix, nenhum banco abre contestação e a análise de risco não reprova cartão. Para o seu sistema receber esses eventos antes de produção, a N7 aplica o desfecho que você pedir numa cobrança sandbox, pelo mesmo caminho de um desfecho real: o status muda, o paid_at é gravado quando for o caso, e o webhook sai para os seus endpoints sandbox com o formato e a assinatura de produção.

POST/api/v1/sandbox/charges/:id/simulate
Só chave n7_sk_sand_. Aceita o id da N7 ou o external_id.
event Parte de Resultado
payment.paid pending paid e webhook payment.paid. Num Pix, a cobrança também é confirmada no sandbox do Asaas, então um estorno depois funciona de ponta a ponta; gateway_synced na resposta diz se isso aconteceu. Espere um ou dois minutos antes de estornar: logo depois do pagamento o Asaas recusa o estorno, e a resposta é 422 refund_declined pedindo para tentar de novo em alguns instantes
payment.denied pending denied e webhook payment.denied: é a recusa assíncrona, a da análise de risco
payment.chargeback paid chargeback e webhook payment.chargeback
curl -X POST https://n7pay.com/api/v1/sandbox/charges/5fac4bcb-4e84-4a90-babf-195419aca231/simulate \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui" \
  -H "Content-Type: application/json" \
  -d '{"event": "payment.paid"}'

# Resposta 200: a transação, como em GET /transactions/:id, mais
# "simulated_event": "payment.paid" e "gateway_synced": true

Estorno não passa pelo simulador: use o endpoint real, POST /charges/:id/refund, que funciona no sandbox e dispara payment.refunded. Com a chave live o simulador responde 403 sandbox_only; um evento fora da tabela, 422 invalid_event; e uma cobrança fora do status de partida, 422 invalid_transition.

O que o sandbox não reproduz. A análise antifraude de produção, que olha o IP, o CEP e o histórico do pagador, e por isso pode recusar em produção um cartão que o sandbox aprovou. E 3DS, que não existe na cobrança de cartão em real pela API: o cartão é aprovado ou recusado na própria chamada.

Roteiro de testes

A mesma sequência serve para os dois ambientes, trocando a chave. No sandbox ela não move dinheiro; em produção, use um cartão seu e um valor baixo, e estorne no fim.

  1. No painel, com o ambiente certo selecionado, crie a chave secreta e cadastre o endpoint de webhook em API & Webhooks. O segredo de assinatura é gerado pela N7, ou você cola o que o seu servidor já usa. Clique em Testar e confira que o seu servidor validou a assinatura do test.ping e respondeu 2xx.
  2. POST /customers com nome, CPF, e-mail, telefone, postal_code e address_number. Guarde o id.
  3. POST /tokens com esse customer_id e o cartão. Guarde o card_token.
  4. POST /charges com method: "card_1x", o card_token, o mesmo customer_id, amount: "5.00" e uma Idempotency-Key. Espere 201 com status: "paid" e, em seguida, o webhook payment.paid.
  5. Repita a cobrança sem ip, que é o caso da cobrança feita sem o pagador presente.
  6. POST /charges/:id/refund em cada cobrança. Espere 201 e o webhook payment.refunded.
  7. Pix: POST /charges com method: "pix". No sandbox, pague pelo simulador; em produção, pague o QR. Espere payment.paid.
  8. Só no sandbox: um cartão de recusa para ver o 422, e o simulador com payment.denied e payment.chargeback para ver os webhooks que produção só manda quando acontecem.
  9. Concilie por GET /transactions, filtrando por external_reference ou status.

Erros

Erros retornam JSON com campo error (código) e message (descrição legível):

{
  "error": "unauthorized",
  "message": "API key inválida ou revogada."
}

O campo description em cobrança no Brasil

Numa cobrança em BRL, a descrição não pode conter emoji nem qualquer outro caractere acima de U+FFFF, que em UTF-8 são as sequências de quatro bytes. É uma restrição do Asaas, e a N7 recusa antes de chamar o gateway para você receber um erro que nomeia o caractere em vez de um parse_error que fala em "caracteres especiais" sem dizer quais.

Acento, cedilha, travessão, cifrão, pontuação e ideograma passam. Só a faixa acima de U+FFFF é recusada. Em cobrança USD pelo Stripe a restrição não existe: ela é do gateway, e a N7 não a impõe a quem não a tem.

// recusado
{ "amount": "50.00", "currency": "BRL", "method": "pix",
  "description": "Ingresso Arena 🎟" }

// resposta 422
{
  "error": "invalid_params",
  "details": {
    "description": "não pode conter 🎟. O Asaas recusa emoji e outros caracteres acima de U+FFFF. Acento, cedilha e pontuação são aceitos."
  }
}

// aceito
{ "amount": "50.00", "currency": "BRL", "method": "pix",
  "description": "Ingresso Arena · São Paulo · lote 1" }
HTTP Código Descrição
422 invalid_params Parâmetros ausentes ou inválidos, listados em details. Em BRL, inclui description com emoji ou caractere acima de U+FFFF
404 customer_not_found O customer_id enviado em POST /charges ou POST /tokens não existe nesta empresa, ou foi criado com a chave do outro ambiente
422 charge_declined O gateway recusou a cobrança. Vale para o cartão negado pelo emissor e também para dado que o gateway não aceitou, como um token usado em outro cliente. O motivo dele vem em details, com code e message
422 tokenization_failed O gateway não emitiu o token: cartão recusado na validação ou tokenização não liberada. O motivo vem em details
401 unauthorized API key ausente, inválida ou revogada
409 request_in_progress Outra requisição com a mesma Idempotency-Key ainda está sendo processada. Aguarde e consulte o resultado; criar outra cobrança duplica o pedido
422 idempotency_key_reused A Idempotency-Key já foi usada com outro corpo. Devolver a cobrança anterior entregaria um valor que não foi pedido: gere uma chave nova
422 card_token_required Cobrança com cartão sem card_token nem o objeto card
422 charge_failed O gateway não respondeu, ou respondeu algo que não é uma recusa. É raro, e é o único erro em que a cobrança pode ter sido criada no gateway sem a N7 registrar: num cartão, não repita sozinho, fale com a N7 antes
422 token_gateway_unavailable O card_token pertence a um gateway que não atende este método e moeda. Tokenização não é portátil: emita o token no provedor certo, ou envie card e deixe o roteamento livre
503 gateway_unavailable O gateway roteado para esta moeda não está configurado neste ambiente. Diferente de no_gateway_available, em que nenhum gateway atende a combinação de método e moeda
403 publishable_key_not_allowed Chave publicável usada fora de POST /tokens
404 not_found Recurso não encontrado
422 validation_failed Cadastro de cliente inválido, inclusive CPF/CNPJ já cadastrado no mesmo ambiente. Os campos vêm em details
429 rate_limit_exceeded Limite de requisições atingido
500 internal_error Erro interno: entre em contato
503 no_gateway_available Nenhum gateway atende essa combinação de método e moeda

Rate Limit

Limites por chave de API: 1000 requisições por minuto em live e 100 em sandbox, em janela deslizante.

Não há headers de quota na resposta. A N7 ainda não envia X-RateLimit-Limit nem equivalentes, então não construa contador em cima deles. Ao estourar, a resposta é 429 rate_limit_exceeded com retry-after: 60, e é esse header que você deve respeitar.

Links são URLs que permitem cobranças sem integração de checkout customizado. Suportam PIX, boleto, cartão (BRL/USD). Podem ter URL personalizada com o slug da empresa: https://n7pay.com/c/sua-empresa/pay/:slug.

GET/api/v1/payment-links
Lista links da empresa. Aceita status e limit, com teto de 200. Não há paginação por page nem filtro por currency ainda.
POST/api/v1/payment-links
Cria um novo link de pagamento.
GET/api/v1/payment-links/:id
Retorna detalhes de um link específico.

Parâmetros de POST /payment-links

Campo Tipo Descrição
title string obrigatório Título na página de pagamento
amount decimal obrigatório Valor: ex: "299.90"
currency string opcional BRL (padrão), USD ou EUR
methods array opcional ["pix"], ["card_1x"], ["boleto"] etc. (padrão: pix)
fee_strategy string opcional "absorb" = merchant paga · "pass_on" = cliente paga
expires_in string opcional "1h", "24h", "3d", "7d". Omitir = sem expiração. Alternativa: expires_at com timestamp ISO 8601.
customer_id uuid opcional ID de cliente N7 para pré-preencher dados
description string opcional Texto auxiliar exibido abaixo do título
settlement_type string opcional "d0", "d1" (padrão) ou "d30"
max_installments integer opcional Teto de parcelas. Omitido, é deduzido do método: card_7_12x → 12, card_2_6x → 6, senão 1.
Cartão parcelado funciona nos dois caminhos. POST /charges aceita card_1x, card_2_6x e card_7_12x direto, dentro da sua UI. O link de pagamento serve quando você prefere não hospedar o checkout. O ambiente (sandbox ou live) é herdado da chave de API usada nos dois casos.
curl -X POST https://n7pay.com/api/v1/payment-links \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Plano Pro · Mensal",
    "amount": "99.90",
    "currency": "BRL",
    "methods": ["pix", "card_1x"],
    "fee_strategy": "absorb"
  }'
# Resposta 201
{
  "data": {
    "id": "9f1c2d3e-4b5a-6789-abcd-ef0123456789",
    "slug": "abc123def",
    "pay_url": "https://n7pay.com/pay/abc123def",
    "title": "Plano Pro · Mensal",
    "description": null,
    "amount": "99.90",
    "currency": "BRL",
    "status": "active",
    "methods": ["pix", "card_1x"],
    "fee_strategy": "absorb",
    "settlement_type": "d1",
    "max_installments": 1,
    "expires_at": null,
    "environment": "sandbox",
    "inserted_at": "2026-06-08T12:00:00"
  }
}

Transações

Transações são criadas automaticamente quando um pagador conclui o fluxo em um link de pagamento ou via API de cobrança direta.

GET/api/v1/transactions
Lista as transações do ambiente da chave, da mais nova para a mais antiga.
Parâmetro Descrição
limit De 1 a 200. Padrão 50
status pending, paid, denied, refunded ou chargeback. Outro valor responde 422 invalid_params
external_reference O seu id do pedido, exatamente como foi enviado na cobrança
starting_after Id da última transação da página anterior. A próxima página começa depois dela
# Pagas de um pedido
curl "https://n7pay.com/api/v1/transactions?external_reference=order_1042&status=paid" \
  -H "Authorization: Bearer n7_sk_live_SuaChaveAqui"

# Resposta 200
{
  "data": [ { "id": "5fac4bcb-...", "status": "paid", "paid_at": "2026-10-07T14:03:11Z", ... } ],
  "has_more": false
}

Para percorrer tudo, repita a chamada com starting_after igual ao id do último item enquanto has_more for true. A página segue a ordem de criação, então uma venda nova entrando no meio da leitura não faz nenhuma transação aparecer duas vezes nem sumir.

GET/api/v1/transactions/:id
Aceita o id da N7 ou o external_id do gateway (o pay_... do Asaas), no ambiente da chave. É também onde o pix_qr_code aparece quando a cobrança voltou com pix_qr_status: "pending": consulte aqui, e não crie outra cobrança. Traz pix_qr_code, pix_qr_image, pix_qr_image_url e pix_qr_status com o mesmo significado da resposta de POST /charges: pare quando virar ready, ou quando virar unavailable.

Datas da transação

Campo O que é
inserted_at Criação da cobrança na N7
paid_at Hora do pagamento, em UTC. No cartão aprovado é a hora da própria cobrança; no Pix, a hora em que o banco confirmou. null enquanto não pagou. É o campo a usar como "pago em"
settled_at Liquidação do valor para você, não pagamento. Hoje vem sempre null: não use como data do pagamento

Status de transação

Status Descrição
pending Aguardando pagamento (PIX/boleto gerado)
processing Pagamento detectado, aguardando confirmação
paid Confirmado pelo gateway
denied Recusado pelo gateway ou pela análise de risco
refunded Estornado
chargeback Disputa aberta pelo titular do cartão

Cobranças diretas: PIX e cartão inline

Use cobranças diretas quando você quer cobrar dentro da sua própria UI, sem redirecionar o pagador para o checkout hospedado. O mesmo endpoint atende PIX e cartão, mudando apenas o method.

Diferença em relação a Links de Pagamento: Links de pagamento redirecionam o pagador para https://n7pay.com/pay/:slug. Cobranças diretas devolvem tudo na resposta: PIX retorna pix_qr_code (copia e cola) e pix_qr_image (PNG base64); cartão retorna o status final já processado. Confira sempre pix_qr_status: em pending a adquirente ainda não gerou o QR, e ele chega em GET /api/v1/transactions/{id}, que devolve o mesmo campo: a espera termina em ready, ou em unavailable se a adquirente não gerar.
POST/api/v1/charges
Cria uma cobrança PIX (QR inline) ou de cartão (com card_token). Resposta síncrona.

Métodos aceitos e parcelamento

O número de parcelas está codificado no nome do método. Não existe card_12x, porque a faixa é que define o teto:

method Tipo Parcelas
pix PIX à vista: retorna QR code
card_1x Cartão de crédito 1× (à vista)
card_2_6x Cartão de crédito 2 a 6×: padrão 6 se installments for omitido
card_7_12x Cartão de crédito 7 a 12×: padrão 12 se installments for omitido
usd Cartão internacional à vista, roteado para o Stripe quando a moeda não é BRL
Boleto não sai por aqui. POST /charges devolve 422 unsupported_method para boleto. Emita boleto por POST /payment-links com methods: ["boleto"].
Envie installments explicitamente para parcelar. O método define só o teto da faixa. Com method: "card_7_12x" e installments: 10, cobra em 10×. Omitindo installments, o teto da faixa é usado (12×). Os mesmos nomes valem no campo methods de payment links, onde o campo equivalente é max_installments.

A moeda decide o gateway

Você não escolhe o gateway. Envia currency e a N7 roteia. O mesmo endpoint serve real e dólar, e a forma da resposta muda conforme quem atendeu.

currency Gateway Métodos O que volta
BRL Asaas pix, card_1x, card_2_6x, card_7_12x pix_qr_code, pix_qr_image, pix_qr_image_url e pix_qr_status no PIX
USD, EUR Stripe usd, card_1x, card_2_6x client_secret para confirmar no navegador
Fora de BRL o documento do pagador não é obrigatório. O Asaas exige CPF ou CNPJ para criar cobrança, então customer.document continua obrigatório em real. O Stripe não exige, e um comprador estrangeiro não tem CPF, então o campo é opcional nas demais moedas. O valor mínimo também acompanha a moeda: R$ 5,00 em real, US$ 0.50 em dólar, € 0,50 em euro.
Com card_token, a cobrança em dólar já volta paga. A N7 confirma no servidor e a resposta vem com status: "paid" e gateway_status: "succeeded", mais card_brand e card_last4. Sem token, a cobrança fica pending e você confirma no navegador com o client_secret, que é o formato certo para checkout hospedado. Aceita um PaymentMethod (pm_...) ou um token de cartão (tok_...).

Se nenhum gateway ativo da sua conta declarar a moeda pedida, a resposta é 503 no_gateway_available. Se um gateway for roteado mas ainda não atender cobrança direta, é 422 gateway_not_supported, que é diferente de unsupported_method: o método estava certo, o gateway é que não atende.

Parâmetros de POST /charges

Campo Tipo Descrição
amount decimal obrigatório Valor em BRL: ex: "49.90". Mínimo R$ 5,00 (limite do Asaas); abaixo disso a resposta é 422 imediato.
method string opcional "pix" (padrão), "card_1x", "card_2_6x", "card_7_12x" ou "usd": ver tabela acima
installments integer opcional Número de parcelas. Só vale para métodos de cartão; deve caber na faixa do método.
card_token string cartão* O formato depende da moeda. Em BRL, o token gerado em POST /tokens. Em moeda estrangeira, um PaymentMethod do Stripe (pm_...) ou um token de cartão (tok_...), obtido no navegador pelo Stripe.js. Os dois não são intercambiáveis, e mandar um no lugar do outro devolve invalid_params dizendo qual era o esperado. Enviar um token restringe o Smart Routing ao gateway que o emitiu; o objeto card abaixo mantém o roteamento livre. *Um dos dois é obrigatório em cartão.
card object cartão* Cartão inline: holder_name, number, exp_month, exp_year, cvv. Use quando não houver card_token.
holder object com card Dados do portador exigidos pelo antifraude: document, postal_code, address_number, phone. Herdados do objeto customer quando presentes lá.
currency string opcional "BRL" (padrão)
description string opcional Descrição exibida no comprovante
external_reference string opcional Seu ID interno para reconciliação
ip string recomendado IP do pagador. Usado pelo antifraude no cartão: sem ele a taxa de recusa sobe.
customer_id uuid opcional Cliente já cadastrado em POST /customers, no mesmo ambiente da chave. Dispensa o objeto customer: nome, e-mail e documento vêm do cadastro. Use sempre que cobrar um cartão salvo: o token só vale no cliente em que foi gerado
customer.name string obrigatório* Nome do pagador. *Dispensado com customer_id
customer.document string obrigatório* CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem máscara: exigido pelo Asaas em toda cobrança em real. *Dispensado com customer_id
customer.email string opcional E-mail para notificações
Qual cliente a cobrança usa. Com customer_id, aquele cliente; um id de outra empresa ou do outro ambiente responde 404 customer_not_found. Sem ele, em real, o cliente com o mesmo CPF/CNPJ no ambiente da chave; não havendo, um novo é criado. O e-mail não identifica ninguém em real: dois cadastros com o mesmo e-mail e CPFs diferentes continuam sendo duas pessoas, e a cobrança vai para a do documento enviado.
curl -X POST https://n7pay.com/api/v1/charges \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "49.90",
    "method": "pix",
    "description": "Pedido #1042",
    "external_reference": "order_1042",
    "customer": {
      "name": "João da Silva",
      "email": "joao@exemplo.com",
      "document": "123.456.789-00"
    }
  }'
# Resposta 201
{
  "data": {
    "id": "5fac4bcb-4e84-4a90-babf-195419aca231",
    "status": "pending",
    "amount": "49.90",
    "currency": "BRL",
    "method": "pix",
    "external_id": "pay_8fHtX3kZq",
    "pix_qr_code": "00020126580014br.gov.bcb.pix0136...",
    "pix_qr_image": "iVBORw0KGgoAAAANSUhEUgAAAcIAAAHCAQ...",
    "pix_qr_image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAHCAQ...",
    "pix_qr_status": "ready",
    "expires_at": "2026-06-08T16:30:00",
    "inserted_at": "2026-06-08T16:00:00Z"
  }
}

Campos da resposta

Campo Tipo Descrição
id string ID da transação no N7 Pay: use para consultar status via GET /transactions/:id
status string pending: aguardando pagamento do PIX
pix_qr_code string Payload EMV (copia e cola): exiba em um campo copiável
pix_qr_image string PNG em base64 cru, sem o prefixo data:. Use para gravar em arquivo ou reprocessar a imagem. Para renderizar, prefira pix_qr_image_url: jogar este campo direto num src resulta em imagem quebrada
pix_qr_image_url string O mesmo PNG já embrulhado em data URI, pronto para <img src="{pix_qr_image_url}">
pix_qr_status string ready: o QR veio nos campos acima. pending: a adquirente ainda não terminou de gerar. A cobrança existe e é válida; consulte GET /api/v1/transactions/{id} até virar ready, e não crie outra cobrança, isso duplica o pedido. unavailable: a adquirente não gerou e não vai gerar; pare de consultar e ofereça o authorization_url. null fora do PIX. O campo vem igual nas duas respostas, então o mesmo código serve para criar e para consultar
client_secret string Presente quando o Stripe atendeu a cobrança (moeda diferente de BRL). Confirme o pagamento no navegador com ele. É o equivalente ao QR do PIX
stripe_publishable_key string Vem junto do client_secret, e é a chave que o Stripe.js usa para confirmá-lo. Já corresponde ao ambiente da cobrança, então não há risco de confirmar um client_secret de teste com uma chave de produção. É pública por definição, pode ir para o HTML
expires_at datetime O QR expira 30 minutos após a criação
external_id string ID da cobrança no gateway (Asaas)
customer_id uuid Cliente da N7 em que a cobrança saiu. É o que você envia de volta para cobrar o cartão salvo no mesmo cliente
external_reference string O seu id do pedido, como foi enviado
paid_at datetime Hora do pagamento, em UTC. Já vem preenchido no cartão aprovado; no Pix, null até o banco confirmar
authorization_url string Só no Pix: a fatura do gateway, plano B quando o QR não vier. No cartão vem null, porque o cartão em real não passa por 3DS na API
Confirmação via webhook: O status pending não significa que o PIX foi pago. Configure o webhook de saída no seu dashboard para receber o evento payment.paid quando o pagamento for confirmado pelo Banco Central.
Split e agrupamento são automáticos. Você não envia nada a mais: o pagador é criado no Asaas dentro do grupo da sua empresa, e o valor líquido é dividido para a sua carteira quando a subconta já está provisionada. Veja Clientes e Split de Receita.

Consultando o status da cobrança

Use o id retornado na criação para consultar o status em qualquer momento. A resposta de GET /transactions/:id inclui os mesmos campos de QR enquanto o PIX estiver pendente.

curl https://n7pay.com/api/v1/transactions/5fac4bcb-4e84-4a90-babf-195419aca231 \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui"

Confirmando uma cobrança do Stripe no navegador

Fora de BRL quem atende é o Stripe, e existem dois caminhos. Eles são exclusivos: escolha um. Em POST /charges/international o card_token é opcional, então não enviá-lo coloca você no segundo caminho.

Com card_token Sem card_token
Quem confirma A N7, no servidor O navegador do pagador, com o Stripe.js
Status na resposta paid pending
Campos que voltam card_brand, card_last4 client_secret, stripe_publishable_key
O que falta fazer Nada Confirmar no navegador passando o cartão
O client_secret não carrega o cartão. Ele identifica a cobrança, não o meio de pagamento. Confirmar só com ele, sem passar o cartão, faz o Stripe responder:

A payment method of type card was expected to be present, but this PaymentIntent does not have a payment method and none was provided. Try again providing either the payment_method or payment_method_data parameters.

A mensagem é do Stripe e aparece na tela do pagador. Não é recusa do cartão nem erro da N7: é confirmação sem cartão. O cartão entra no segundo argumento de confirmCardPayment, como abaixo.
// 1. Monte o campo de cartão com a chave que veio na cobrança
const stripe = Stripe(cobranca.stripe_publishable_key);
const cardElement = stripe.elements().create("card");
cardElement.mount("#card-element");

// 2. Confirme passando o client_secret E o cartão
const { error, paymentIntent } = await stripe.confirmCardPayment(
  cobranca.client_secret,
  {
    payment_method: {
      card: cardElement,
      billing_details: { name: "Maria Silva", email: "maria@email.com" }
    }
  }
);

if (error) {
  // cartão recusado, ou 3DS cancelado pelo pagador
  mostrarErro(error.message);
} else if (paymentIntent.status === "succeeded") {
  // autorizado. O status na N7 chega pelo webhook payment.paid
  aguardarConfirmacao(cobranca.id);
}
POST /tokens não serve para moeda estrangeira. Ele emite token do Asaas, e o Asaas não atende USD nem EUR. Enviar esse token numa cobrança em dólar prende o Smart Routing a um gateway que não pode atendê-la, e a resposta é 422 token_gateway_unavailable. Para usar o caminho com card_token fora de BRL, o token tem que nascer no Stripe.js do seu navegador, como pm_... ou tok_..., usando a stripe_publishable_key que a cobrança devolve.
succeeded no navegador ainda não é paid na N7. O navegador sabe que o cartão foi autorizado; a transação na N7 muda de pending para paid quando o webhook do Stripe chega. Até lá, consulte GET /api/v1/transactions/{id}. Não crie outra cobrança enquanto espera: isso abre um segundo PaymentIntent para o mesmo pedido, e os dois podem ser pagos.

Tokenização de cartão

Tokenizar prende a cobrança a um gateway. O token é uma referência que só a adquirente que o emitiu sabe abrir: um token do Asaas não vale no Stripe, nem no Mercado Pago, nem no Pagar.me. Quando você envia um card_token, o Smart Routing deixa de escolher entre os gateways disponíveis e fica restrito ao dono do token. Se ele não puder atender aquela cobrança, a resposta é 422 token_gateway_unavailable em vez de uma recusa sem explicação na adquirente.

Enviar o cartão em card no próprio POST /charges mantém o roteamento livre, e é o caminho que aproveita o Smart Routing por inteiro. O preço é ficar no escopo de PCI. Tokenizar tira você desse escopo e custa a liberdade de roteamento. Os dois são legítimos; a escolha é sua, e é bom que seja consciente.

Cobrança com cartão nunca precisa trafegar o número do cartão pelo seu servidor: ele pode ser trocado antes por um card_token em POST /tokens. Você escolhe onde essa troca acontece, e a escolha tem consequência de compliance.

POST/api/v1/tokens
Tokeniza um cartão. Aceita chave publicável (navegador) ou secreta (servidor).

Dois caminhos

Do navegador Do seu servidor
Chave n7_pk_… (publicável) n7_sk_… (secreta, a que você já tem)
Quem vê o cartão Só o navegador do pagador e a N7 Também os seus servidores
Escopo PCI-DSS SAQ A-EP (reduzido) SAQ D (completo: auditoria anual, pentest)
Variável de ambiente Precisa de uma nova Nenhuma: reusa a que existe
Se o cartão passar pelo seu servidor, você entra em PCI-DSS SAQ D. É uma obrigação anual recorrente, não um detalhe de implementação. Tokenizar no navegador é mais trabalho uma vez e evita isso para sempre. Comece pelo servidor se precisar destravar rápido, mas planeje migrar.

Caminho A: do navegador (recomendado)

  1. Seu servidor cria (ou busca) o cliente em POST /customers e devolve o customer_id para a sua página.
  2. O navegador do pagador chama POST /tokens com a chave publicável e os dados do cartão, e recebe card_token.
  3. Sua página envia apenas o card_token ao seu servidor.
  4. Seu servidor chama POST /charges com a chave secreta, o card_token, o method e as installments.

A chave publicável é gerada no dashboard em Chaves API → Tipo → Publicável. Ela pode ficar visível no código da sua página: só tokeniza cartão, não cria cobrança nem lê dados. Isso é imposto pela API, não é convenção: usar uma chave publicável em qualquer outro endpoint devolve 403 publishable_key_not_allowed. O endpoint responde com CORS, então o fetch cross-origin funciona.

Caminho B: do seu servidor

Se o formulário de cartão já é processado no seu backend, chame POST /tokens de lá com a mesma chave secreta que você usa em /charges, sem precisar criar chave nem variável nova. O corpo é idêntico ao do caminho A.

Caminho C: cartão inline, roteamento livre

Envie o cartão direto no POST /charges, no objeto card, e omita o card_token. É o único caminho em que a N7 escolhe o gateway pelo custo no momento da cobrança, porque nada foi decidido antes. Também é o caminho a usar quando a tokenização ainda não estiver liberada na sua conta pelo gateway.

Em troca, o número do cartão passa pelo seu servidor, e isso coloca você no escopo de PCI DSS. Se esse escopo for um problema, use o caminho A e aceite a restrição de roteamento.

curl -X POST https://n7pay.com/api/v1/charges \
  -H "Authorization: Bearer n7_sk_live_SuaChaveAqui" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "297.00",
    "method": "card_7_12x",
    "installments": 12,
    "ip": "189.10.20.30",
    "customer": {
      "name": "Maria Souza",
      "email": "maria@example.com",
      "document": "982.671.040-70",
      "phone": "61992746281",
      "postal_code": "71735103",
      "address_number": "17"
    },
    "card": {
      "holder_name": "MARIA S SOUZA",
      "number": "5162306219378829",
      "exp_month": "05",
      "exp_year": "2031",
      "cvv": "318"
    }
  }'

Os dados do portador saem do objeto customer; se o titular do cartão for outra pessoa, mande um objeto holder separado. Campos faltando voltam como 422 listando exatamente quais, antes de qualquer chamada ao gateway.

O efeito em compliance é o mesmo do caminho B, porque o cartão passa pelo seu servidor.

Atenção com LiveView e formulários tradicionais. Se o campo do cartão faz submit normal ou trafega por um socket LiveView, o número já chegou ao seu servidor, ou seja, você está no caminho B mesmo tendo chave publicável. Para o caminho A valer, a tokenização precisa ser um fetch em JS direto para a N7, e só o token pode ser enviado ao seu backend.

Parâmetros de POST /tokens

Campo Tipo Descrição
customer_id uuid obrigatório Cliente N7 dono do cartão, criado com a chave do mesmo ambiente. O token fica preso a ele
card.holder_name string obrigatório Nome impresso no cartão
card.number string obrigatório Número do cartão (com ou sem espaços)
card.exp_month string obrigatório "05" ou "5"
card.exp_year string obrigatório "2031" ou "31": aceitamos os dois
card.cvv string obrigatório Código de segurança
holder.document string obrigatório* CPF/CNPJ do portador. *Todos os campos holder são herdados do cliente quando ele já os tem: document, phone, postal_code e address_number enviados em POST /customers. Mande holder só quando o titular do cartão for outra pessoa
holder.postal_code string obrigatório* CEP do portador: exigido pelo antifraude
holder.address_number string obrigatório* Número do endereço
holder.phone string obrigatório* Telefone com DDD
ip string opcional IP do pagador. Detectado automaticamente se omitido.
// Caminho A · no navegador do pagador, com a chave publicável.
// No caminho B, a mesma chamada sai do seu servidor com a chave n7_sk_.
const res = await fetch("https://n7pay.com/api/v1/tokens", {
  method: "POST",
  headers: {
    "Authorization": "Bearer n7_pk_sand_SuaChavePublicavel",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    customer_id: "44dcf7a7-ebc3-4ad7-b122-01f404cda545",
    card: {
      holder_name: "MARIA S SOUZA",
      number: "5162306219378829",
      exp_month: "05",
      exp_year: "2031",
      cvv: "318"
    },
    holder: {
      document: "982.671.040-70",
      postal_code: "71735103",
      address_number: "17",
      phone: "61992746281"
    }
  })
});

// Resposta 201
{
  "data": {
    "card_token": "n7tok_asaas_8a1b2c3d-...",
    "gateway": "asaas",
    "brand": "MASTERCARD",
    "last4": "8829",
    "customer_id": "44dcf7a7-ebc3-4ad7-b122-01f404cda545"
  }
}

Guarde o card_token inteiro, com o prefixo: ele diz a que gateway o token pertence. É ele que permite cobrar de novo sem pedir o cartão ao pagador.

O token só vale no cliente em que foi gerado. Cobrar com ele em outro cliente volta 422 charge_declined com "CreditCardToken não encontrado". Por isso a cobrança pelo cartão salvo leva o mesmo customer_id usado em POST /tokens. Sem customer_id, a N7 usa o cliente com o mesmo CPF/CNPJ, o que também funciona desde que o documento enviado seja o mesmo do cadastro.

Cobrando o cartão tokenizado

# No seu servidor · chave secreta
curl -X POST https://n7pay.com/api/v1/charges \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1042-tentativa-1" \
  -d '{
    "amount": "297.00",
    "method": "card_7_12x",
    "installments": 12,
    "customer_id": "44dcf7a7-ebc3-4ad7-b122-01f404cda545",
    "card_token": "n7tok_asaas_8a1b2c3d-...",
    "external_reference": "payment_123",
    "ip": "189.10.20.30"
  }'
# Resposta 201 · cartão é síncrono, o status já é final
{
  "data": {
    "id": "uuid",
    "status": "paid",
    "method": "card_7_12x",
    "installments": 12,
    "amount": "297.00",
    "external_id": "pay_yxvvt5xg2tlzaong",
    "gateway_status": "CONFIRMED",
    "card_brand": "MASTERCARD",
    "card_last4": "8829",
    "customer_id": "44dcf7a7-ebc3-4ad7-b122-01f404cda545",
    "external_reference": "payment_123",
    "paid_at": "2026-10-07T14:03:11Z",
    "authorization_url": null,
    "environment": "sandbox"
  }
}
Pergunta Resposta
Precisa de CVV? Não. O CVV entra só em POST /tokens; a cobrança pelo token não o pede
E sem o pagador presente? Funciona. Sem ip a cobrança é aceita (verificado no sandbox). Quando o pagador estiver na sua página, mande o IP dele: o antifraude de produção usa o IP, e sem ele a chance de recusa sobe. Nunca mande o IP do seu servidor
Vai marcada como recorrente? Não. É uma cobrança de cartão comum, sem marcação de recorrência
Pode pedir 3DS? Não. O cartão em real é aprovado ou recusado na própria chamada, e authorization_url vem null

Aprovação e recusa

Cartão aprovado já volta status: "paid", sem tela de "processando". Diferente do PIX, que nasce pending e só vira pago pelo webhook, o cartão traz o veredito do emissor na própria resposta. Se a sua UI mostra "aguardando confirmação" depois de um 201 de cartão, ela está esperando um evento que já aconteceu.

Recusa não é 201. Quando o emissor nega, a resposta é 422 com o motivo dele, e não um 201 com status negado. Trate os dois caminhos separadamente:

# 422 · recusado
{
  "error": "charge_declined",
  "details": [
    {
      "code": "invalid_action",
      "message": "Transação não autorizada. Verifique os dados do cartão de crédito e tente novamente."
    }
  ]
}

A recusa na chamada não cria transação nem dispara webhook: a cobrança não chegou a existir, e o 422 é o único aviso. charge_declined cobre toda recusa do gateway, a do emissor e a de dado que ele não aceitou, como um token usado em outro cliente; a diferença está em details. Mostre ao pagador a message, e ofereça outro cartão.

Os status possíveis num 201:

status Significado
paid Aprovado e capturado: pode liberar o produto. O webhook payment.paid também sai, uma vez, para o caso de a resposta se perder no caminho
pending Foi para análise manual de risco do Asaas. Não há nada para o pagador fazer: o desfecho chega por webhook, payment.paid ou payment.denied

payment.denied existe só para esse caso, a recusa que chega depois. Em sandbox, exercite os dois caminhos com os cartões de recusa, que produzem o 422, e com o simulador, que produz o payment.denied.

Clientes

Clientes representam os pagadores da sua empresa. Ao criar um cliente, o N7 Pay sincroniza automaticamente com os gateways configurados (Asaas, Stripe), armazenando os IDs remotos para acelerar cobranças futuras e habilitar split de receita.

Campos para gateways: Asaas exige document (CPF/CNPJ) para PIX. Stripe exige email + endereço de cobrança para cartões internacionais. Sempre capture o máximo de dados para cobrir ambos os casos.

Isolamento entre empresas

Seus pagadores são seus. Mesmo com várias empresas usando a plataforma, o cadastro de um CPF que compra de você é separado do cadastro do mesmo CPF comprando de outra empresa. Não há reuso de cliente entre contas, nem no N7 nem no gateway.

O identificador remoto fica em gateway_customer_ids e é reaproveitado nas cobranças seguintes, o que deixa a segunda compra do mesmo pagador mais rápida. Você não precisa gerenciar esse campo: ele é preenchido sozinho.

Todo cliente pertence ao ambiente da chave que o criou. A listagem, a consulta e a atualização só enxergam o ambiente da chave usada: o mesmo CPF pode ter um cadastro no sandbox e outro em produção, e cada chave vê só o seu. É o que garante que um cartão salvo em produção nunca seja cobrado num cliente de teste.

GET/api/v1/customers
Lista clientes da empresa no ambiente da chave. Filtros: document (com ou sem máscara), email, country e limit (até 200).
POST/api/v1/customers
Cria cliente e inicia sincronização com gateways em background.
GET/api/v1/customers/:id
Retorna cliente com gateway_customer_ids mapeados.
PUT/api/v1/customers/:id
Atualiza dados do cliente.

Parâmetros de POST /customers

Campo Tipo Descrição
name string obrigatório Nome completo ou razão social
email string opcional* *Obrigatório para Stripe
phone string opcional Com DDI, ex: +5511999999999
document_type string opcional "cpf", "cnpj", "vat", "ssn", "passport"
document string opcional* *Obrigatório para PIX (CPF/CNPJ)
country string opcional ISO 3166: ex: "BR", "US", "PT" (padrão: "BR")
address_line1 string opcional Logradouro. Obrigatório para Stripe.
city string opcional Cidade
state string opcional Estado / UF
zip_code string opcional CEP ou postal code. postal_code, o nome usado no resto da API, é aceito como sinônimo
address_number string opcional Número do endereço. Fica em metadata.address_number e é herdado por POST /tokens, junto do CEP e do telefone, para você não precisar mandar holder
timezone string opcional IANA: ex: "America/Sao_Paulo" (padrão)
# Criar cliente (PIX + cartão internacional)
curl -X POST https://n7pay.com/api/v1/customers \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "João da Silva",
    "email": "joao@exemplo.com",
    "phone": "+5511999999999",
    "document_type": "cpf",
    "document": "123.456.789-00",
    "country": "BR",
    "address_line1": "Rua das Flores, 100",
    "city": "São Paulo",
    "state": "SP",
    "zip_code": "01310-100",
    "address_number": "100"
  }'
# Resposta 201
{
  "data": {
    "id": "44dcf7a7-ebc3-4ad7-b122-01f404cda545",
    "name": "João da Silva",
    "email": "joao@exemplo.com",
    "document": "123.456.789-00",
    "country": "BR",
    "zip_code": "01310-100",
    "environment": "sandbox",
    "gateway_customer_ids": {},
    "metadata": { "address_number": "100" },
    "created_at": "2026-06-08T15:00:00"
  }
}

gateway_customer_ids nasce vazio e é preenchido em segundo plano, segundos depois, ou na primeira tokenização ou cobrança, o que vier antes. Você não precisa esperar por ele: o id da N7 já serve para POST /tokens e POST /charges.

Webhooks de saída (N7 para o seu servidor)

Configure endpoints HTTPS no seu dashboard para receber o desfecho de cada cobrança. É por aqui que você sabe que um PIX foi pago: a resposta de POST /charges devolve pending, e a confirmação chega depois.

O caminho completo de um pagamento, e onde entra cada um.
1. você      POST /charges                    → recebe id e QR, status pending
2. comprador paga o PIX no banco dele
3. Asaas     avisa a N7                       ← webhook da N7, já configurado
4. N7        marca a transação como paid
5. N7        POST no SEU endpoint              → payment.paid, assinado
6. você      emite o ingresso

Você cadastra só o endereço do passo 5, no seu dashboard. O passo 3 é entre a N7 e a adquirente, na conta da N7, e já está de pé: você não abre conta no Asaas nem no Stripe, não cadastra webhook lá e não recebe token deles. Se algum material pedir que você configure algo no painel de uma adquirente, ele está errado.

Enquanto não montar o passo 5, dá para trabalhar consultando GET /transactions/:id. O webhook evita a consulta em laço, não substitui uma coisa que não existe.

O que você configura

Só duas coisas, no painel da N7: a URL que recebe os eventos e o segredo que assina cada entrega. Nada no Asaas, nada no Stripe.

  1. No topo do painel, escolha o ambiente: Sandbox ou Produção. O endpoint é cadastrado no ambiente que estiver selecionado, e só recebe eventos daquele ambiente.
  2. Em API & Webhooks, clique em Adicionar endpoint. Informe a URL (HTTPS, pública) e, se quiser, os eventos; vazio assina todos.
  3. No campo Segredo de assinatura, cole o segredo que o seu servidor já usa para validar o X-N7-Signature, com pelo menos 16 caracteres, ou deixe em branco para a N7 gerar um. O segredo aparece na lista de endpoints; clique nele para copiar.
  4. Clique em Testar. A N7 envia um test.ping assinado com o mesmo segredo e os mesmos cabeçalhos de um evento real, e mostra o HTTP que o seu servidor respondeu.

Para receber sandbox e produção na mesma URL, cadastre o endpoint duas vezes, uma em cada ambiente, com o mesmo segredo ou com segredos diferentes. O cabeçalho X-N7-Environment e o campo environment do corpo dizem de qual ambiente veio cada evento.

Headers de cada entrega

Header Conteúdo
X-N7-Signature HMAC-SHA256 em hexadecimal, com o prefixo sha256=
X-N7-Timestamp Unix em segundos. Entra no cálculo da assinatura
X-N7-Event Nome do evento, o mesmo do campo event
X-N7-Delivery UUID da entrega, o mesmo em todas as tentativas dela. Use para descartar reentregas
X-N7-Environment sandbox ou live
A assinatura não é sobre o corpo puro. O valor assinado é "{timestamp}.{corpo}", e o header vem com o prefixo sha256=, que precisa ser removido antes de comparar. Assinar só o corpo, ou comparar com o prefixo, faz toda verificação falhar.

Verificando a assinatura

// Node.js · Express, com o corpo cru preservado
// app.use("/webhooks/n7", express.raw({ type: "application/json" }));
const crypto = require("crypto");

function verify(rawBody, headers, secret) {
  const received = String(headers["x-n7-signature"] || "").replace(/^sha256=/, "");
  const timestamp = String(headers["x-n7-timestamp"] || "");

  // rejeita entregas antigas, contra replay
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + rawBody)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
# Elixir
def valid_signature?(raw_body, headers, secret) do
  received = headers |> Map.get("x-n7-signature", "") |> String.replace_prefix("sha256=", "")
  timestamp = Map.get(headers, "x-n7-timestamp", "")

  expected =
    :crypto.mac(:hmac, :sha256, secret, "#{timestamp}.#{raw_body}")
    |> Base.encode16(case: :lower)

  Plug.Crypto.secure_compare(expected, received)
end
# Ruby on Rails · o corpo cru vem de request.raw_post, nunca de params.to_json
def n7_signature_valid?(request, secret)
  received  = request.headers["X-N7-Signature"].to_s.delete_prefix("sha256=")
  timestamp = request.headers["X-N7-Timestamp"].to_s
  return false if (Time.now.to_i - timestamp.to_i).abs > 300

  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{request.raw_post}")
  ActiveSupport::SecurityUtils.secure_compare(expected, received)
end

Formato do payload

{
  "event": "payment.paid",
  "environment": "live",
  "occurred_at": "2026-10-07T14:03:12",
  "data": {
    "transaction_id": "5fac4bcb-4e84-4a90-babf-195419aca231",
    "payment_link_id": null,
    "external_id": "pay_yxvvt5xg2tlzaong",
    "external_reference": "order_99001",
    "status": "paid",
    "method": "card_1x",
    "currency": "BRL",
    "amount": "5.00",
    "fee_amount": "0.40",
    "net_amount": "4.60",
    "installments": 1,
    "paid_at": "2026-10-07T14:03:11Z",
    "gateway": "Asaas",
    "customer": {
      "id": "44dcf7a7-ebc3-4ad7-b122-01f404cda545",
      "name": "Maria Souza",
      "email": "maria@example.com",
      "document_type": "cpf"
    },
    "metadata": {
      "external_reference": "order_99001",
      "card_brand": "VISA",
      "card_last4": "4444",
      "paid_at": "2026-10-07T14:03:11Z"
    }
  }
}
Amarre pelo external_reference ou pelo transaction_id. O external_reference vem em data.external_reference (e, como sempre veio, em data.metadata.external_reference) e liga o evento ao pedido do seu lado. O transaction_id é o id devolvido em POST /charges, um UUID. O data.status é o que o evento anuncia; para saber o estado atual, consulte GET /transactions/:id.

Eventos disponíveis

Assinar uma lista vazia significa receber todos. Hoje apenas quatro chegam a ser entregues, porque só eles representam mudança de desfecho de uma transação. Cada um sai no máximo uma vez por transação, mesmo quando o gateway avisa a N7 mais de uma vez: o cartão, por exemplo, é confirmado na aprovação e de novo quando o dinheiro cai, e você recebe um payment.paid só.

Evento Entregue? Disparado quando
payment.paid sim Pagamento confirmado. No Pix e no boleto, quando o banco confirma; no cartão, logo depois do 201 aprovado, ou quando a análise de risco aprova
payment.denied sim Cartão que estava pending em análise de risco e foi reprovado. A recusa na própria chamada é o 422, e não gera webhook
payment.refunded sim Estorno que zerou o saldo da venda, feito pela API, pelo painel ou pelo gateway
payment.chargeback sim Disputa aberta pelo titular do cartão
payment.partially_refunded aceito Reservado, ainda não entregue. O estorno parcial não muda o desfecho: a venda continua paid, com saldo estornável menor
payment.created aceito Reservado, ainda não entregue
payment.updated aceito Reservado, ainda não entregue
payment.overdue aceito Reservado, ainda não entregue
transfer.completed aceito Reservado, ainda não entregue
transfer.failed aceito Reservado, ainda não entregue

Reentrega

Uma entrega conta como recebida quando o seu servidor responde 2xx em até 10 segundos. Qualquer outra resposta, timeout ou erro de conexão é repetido, com o mesmo corpo e o mesmo X-N7-Delivery, até oito tentativas: na hora, e depois de 30 segundos, 2 minutos, 10 minutos, 30 minutos, 1 hora, 2 horas e 4 horas. A última sai umas oito horas depois da primeira. Cada tentativa aparece no painel, com o HTTP que o seu servidor respondeu.

Responda 2xx antes de processar, e trate repetição. Grave o evento, responda, e processe depois. Descarte o X-N7-Delivery já visto, e trate o mesmo evento da mesma transação como já processado. A ordem de chegada não é garantida entre eventos diferentes; na dúvida, o estado atual está em GET /transactions/:id. Um endpoint pausado ou removido no painel deixa de receber, inclusive as reentregas pendentes.
O test.ping do painel não é um evento de pagamento. Ele chega com event: "test.ping" e data.message, assinado como os demais. Responda 2xx a eventos que você não trata, em vez de recusá-los: um 4xx vira falha no painel.

Cotação de câmbio

A cotação é um recurso persistido, não um cálculo na hora: ela é travada, tem validade curta e é de uso único. Sem isso a taxa muda entre a tela que o pagador viu e a cobrança que ele recebeu, e a diferença sai do bolso de alguém.

Consulta é outra coisa. Para só olhar o câmbio existe GET /cotacao.json, público e sem chave. Ele devolve referência, não trava taxa, e não serve para cobrar.
POST/api/v1/fx/quotes
Cria a cotação travada, com validade e uso único.
GET/api/v1/fx/quotes/:id
Releitura. Confira status e expires_in imediatamente antes de cobrar.

Parâmetros

Campo Tipo Obrigatoriedade Descrição
from_currency string obrigatório O que o pagador entrega
to_currency string obrigatório O que a venda vale
amount decimal obrigatório Valor, na moeda que o side fixa
side string opcional target (padrão) fixa quanto você recebe · source fixa quanto o pagador entrega
fee_strategy string opcional absorb (padrão) você paga a tarifa · pass_on o pagador paga
method string opcional Método da cobrança futura, para o breakdown trazer a taxa certa
ttl_seconds integer opcional Validade. Padrão 120, mínimo 30, máximo 900

O header Idempotency-Key é honrado: a mesma chave com o mesmo corpo devolve a mesma cotação, em vez de abrir posição cambial duas vezes. A mesma chave com corpo diferente é recusada com 422 idempotency_key_reused.

A chave fica presa à cotação, e cotação vence. Aqui não existe a janela de 24 horas que vale em POST /charges: a chave fica ligada àquela cotação para sempre. Reusá-la depois que a cotação venceu devolve a mesma cotação vencida, não emite outra. Ela volta com expires_in: 0 e status: "active" (o status é o do registro, não uma promessa de que ainda dá para usar), e cobrar com ela falha em 422 quote_expired.

Por isso a chave aqui é por tentativa de cotação, e não por pedido. Um pedido vive minutos ou horas; uma cotação vive ttl_seconds, por padrão bem menos. Versione o valor (pedido_88213.v1, .v2) ou use um UUID novo a cada tentativa, e confira expires_in antes de cobrar.

POST /api/v1/fx/quotes
Authorization: Bearer n7_sk_sand_…
Idempotency-Key: pedido_88213.v1

{
  "from_currency": "BRL",
  "to_currency": "USD",
  "amount": "50.00",
  "side": "target",
  "method": "pix",
  "ttl_seconds": 120
}

# Resposta 201
{
  "data": {
    "id": "24575497-3b27-4ded-9ceb-7c475a051ae8",
    "status": "active",
    "amount": "50.00",
    "quoted_amount": "261.49",
    "base_rate": "0.19503442",
    "spread_pct": "2.00",
    "effective_rate": "0.19121022",
    "expires_in": 119,
    "rate": { "source": "ptax", "observed_at": "2026-09-02T13:02:37Z", "stale": false },
    "breakdown": {
      "base_rate": "0.19503442",
      "rate_source": "ptax",
      "spread_pct": "2.00",
      "spread_amount": "5.12",
      "amount_without_spread": "256.37",
      "charged_amount": "261.49",
      "gateway_fee": "2.39",
      "iof": null,
      "iof_note": "Não há operação de câmbio nesta cobrança: o valor entra e sai na mesma moeda."
    }
  }
}

Duas coisas decidem quem paga o quê

side decide quem come o spread. Em target o pagador entrega mais, em source você recebe menos. O spread é receita da N7 nos dois casos.

fee_strategy decide quem paga a tarifa, exatamente como no link de pagamento. Com pass_on e side: target, você recebe exatamente o valor que pediu.

side fee_strategy Pagador entrega Você recebe
target pass_on R$ 278,81 US$ 50,00, exatamente o pedido
target absorb R$ 276,42 US$ 49,56
source pass_on R$ 273,39 US$ 49,02
source absorb R$ 271,00 US$ 48,58

Exemplo com US$ 50,00 a R$ 5,42, spread de 2% e tarifa Pix de R$ 2,39.

quoted_amount troca de moeda conforme o side. Em target ele é o que o pagador entrega, em source é o que você recebe. Por isso a resposta traz charge e settlement com valor e moeda próprios. Use esses dois se você suporta os dois sentidos.
O breakdown existe para você explicar ao pagador. Câmbio base, spread da N7 e taxa do meio de pagamento vêm separados, em dinheiro e não só em percentual, para o checkout mostrar de onde vem cada centavo em vez de um número final sem explicação. O IOF vem nulo porque nesta rota não há operação de câmbio: o comprador paga em real e o lojista recebe em real, e o dólar é apenas a unidade de conta do preço.

Erros

A regra que amarra todos: nunca recotar em silêncio.

HTTP Código Quando O que fazer
422 unsupported_currency_pair Par não habilitado. Hoje só pares com BRL Não retentar, é configuração
422 amount_below_minimum Abaixo do piso da moeda Ajustar. A mensagem traz o mínimo
422 amount_above_maximum Acima do teto de exposição cambial Dividir ou falar com o comercial
422 idempotency_key_reused Mesma chave, corpo diferente Gerar chave nova
422 invalid_amount Valor não numérico ou menor que zero Corrigir
503 fx_provider_unavailable Fonte fora do ar e sem taxa recente em cache Backoff. Nunca assumir uma taxa
Cotação com taxa velha vem sinalizada, não recusada. Se a fonte cair mas houver taxa recente em cache, a resposta ainda é 201, com rate.stale: true e a validade cortada pela metade. Não é erro, e você decide se aceita. Ignorar esse campo é aceitar preço de mercado defasado sem saber.
A fonte é o PTAX do Banco Central, que é a referência oficial para o real. Ele é publicado ao longo do dia útil, não a cada segundo, então rate.observed_at viaja junto para você julgar a defasagem. Em fim de semana e feriado vale a última publicação.

Cobrança internacional

Cobra em real uma venda precificada em moeda estrangeira, consumindo uma cotação travada. É aqui que o quote_id vira cobrança.

POST/api/v1/charges/international
Consome a cotação e cobra em BRL pelo valor que ela fixou.
Por que não é o POST /charges de sempre. Lá o campo currency acumula três papéis: chave de roteamento, valor persistido e moeda implícita do piso. Numa cobrança internacional os três divergem, e enfiar isso no fluxo existente faria o caminho que já funciona compartilhar validação e cálculo de tarifa com um caminho novo.
Campo Tipo Obrigatoriedade Descrição
quote_id string obrigatório Id de POST /api/v1/fx/quotes
amount decimal recomendado Confere contra a cotação. Divergência devolve quote_mismatch
currency string recomendado Moeda da venda. Confere contra a cotação
method string opcional Padrão: o método da cotação
customer object obrigatório Como em /charges. CPF exigido porque a cobrança sai em real, pelo Asaas
POST /api/v1/charges/international
{
  "quote_id": "867e72a5-330b-4267-8d45-c1162392cf0a",
  "amount": "50.00",
  "currency": "USD",
  "method": "pix",
  "external_reference": "order_arena_1",
  "customer": { "name": "Ana Souza", "email": "ana@exemplo.com", "document": "12345678909" }
}

# Resposta 201
{
  "data": {
    "status": "pending",
    "amount": "263.88",
    "currency": "BRL",
    "fee_amount": "2.39",
    "fee_pct": "0.00",
    "fee_fixed": "2.39",
    "pix_qr_code": "00020101021226820014br.gov.bcb.pix…",
    "pix_qr_image_url": "data:image/png;base64,iVBORw0KGgo…",
    "pix_qr_status": "ready",
    "fx": {
      "original_amount": "50.0000",
      "original_currency": "USD",
      "rate": "0.19503442",
      "effective_rate": "0.19121022",
      "spread_pct": "2.00",
      "settlement_currency": "BRL",
      "breakdown": {
        "charge_currency": "BRL",
        "charge_amount": "263.88",
        "settlement_currency": "USD",
        "settlement_amount": "50.00",
        "spread_amount": "5.15",
        "gateway_fee": "2.39"
      }
    }
  }
}

O objeto fx

Campo Descrição
original_amount O valor que você precificou, no original_currency. É o que a cotação travou
original_currency A moeda em que você precificou. Nesta rota, nunca BRL
rate A taxa de mercado observada, antes do spread
effective_rate A taxa que de fato produziu o amount, já com o spread aplicado. É esta que reconcilia
spread_pct O spread N7 aplicado sobre a taxa de mercado, em porcento
settlement_currency A moeda em que VOCÊ é liquidado. Nesta rota é sempre BRL, porque a cobrança sai em real: o pagador paga um PIX em reais e é em reais que o dinheiro entra na sua conta. Nenhum dólar se move. Veja Em que moeda você recebe
breakdown A conta item a item, para você mostrar ao pagador de onde vem cada centavo
settlement_currency aparece duas vezes, com sentidos opostos. O fx.settlement_currency é a moeda em que você recebe: BRL. O fx.breakdown.settlement_currency é o lado alvo do câmbio, ou seja, a moeda que você precificou: USD. O breakdown é a memória de cálculo da cotação e fala a língua do câmbio; o campo de fora fala da sua liquidação. Para saber em que moeda o dinheiro chega até você, leia sempre o de fora.
A tarifa aplicada viaja junto. fee_pct e fee_fixed são a regra que produziu o fee_amount, congelada no instante da cobrança. O tarifário muda quando o gateway reajusta, e sem esses dois campos não haveria como responder a que taxa uma venda de três meses atrás saiu. Eles também aparecem em GET /transactions e GET /transactions/:id.
amount e currency continuam sendo o que foi cobrado, em real. A venda em dólar fica em fx, então nenhuma agregação que você já faz muda de sentido.

Erros do fluxo internacional

HTTP Código Quando
404 quote_not_found Cotação inexistente, de outra empresa, ou quote_id ausente
422 quote_expired Venceu. Peça outra, nunca reutilize a taxa
422 quote_already_consumed Cada cotação vale uma cobrança
422 quote_mismatch Valor ou moeda diferentes dos cotados
Falha no gateway devolve a cotação. A reserva acontece antes de chamar o gateway, para duas cobranças não usarem a mesma taxa. Se o gateway recusar ou cair, a cotação volta para active e você tenta de novo com o mesmo preço, em vez de mostrar outro ao pagador.

Estornos

Estorna uma cobrança paga, total ou parcial. Parciais podem ser repetidos até somar o valor da venda. Funciona nos dois gateways.

POST/api/v1/charges/:id/refund
Aceita o id da N7 ou o external_id do gateway.
Campo Tipo Obrigatoriedade Descrição
amount decimal opcional Omitir estorna todo o saldo ainda não estornado
reason string opcional Motivo livre, guardado no registro do estorno e devolvido na resposta
O gateway não devolve a taxa que cobrou. Isso não é política da N7, é como Asaas e Stripe operam, e foi verificado contra as duas APIs: uma venda de US$ 50,00 com taxa de US$ 1,75 gera um estorno que devolve US$ 0,00 de taxa, e uma venda de R$ 100,00 com taxa de R$ 3,48 continua com netValue 96,52 depois do estorno. Numa venda estornada, o custo do gateway é perdido por alguém.

Quem fica com a taxa

Por padrão a N7 devolve a própria margem e retém apenas o custo do gateway. A N7 empata, você perde só o custo real da transação, e ninguém lucra com um estorno. A resposta mostra os três números para você conseguir explicar a diferença ao seu cliente e lançar na contabilidade.

# Venda de US$ 120,00, tarifa N7 de US$ 4,98, taxa do gateway de US$ 3,78
# Estorno de US$ 80,00

POST /api/v1/charges/14ac03e6-d431-411b-b732-beedc125826c/refund
{ "amount": "80.00", "reason": "requested_by_customer" }

# Resposta 201
{
  "data": {
    "id": "7c31703b-c5b1-42e0-aaea-d8eaacda632e",
    "transaction_id": "14ac03e6-d431-411b-b732-beedc125826c",
    "status": "done",
    "amount": "80.00",
    "currency": "USD",
    "gateway_refund_id": "re_3UBHe6EWFlHBWTiI0pja5fzG",
    "gateway_fee_retained": "2.52",
    "n7_fee_refunded": "0.80",
    "net_returned_to_merchant": "77.48",
    "refundable_balance": "0.00"
  }
}
Campo O que significa
gateway_fee_retained Custo que o gateway ficou, rateado pela fração estornada. Não volta para ninguém
n7_fee_refunded Quanto da tarifa N7 voltou para você
net_returned_to_merchant Quanto saiu do seu saldo de fato. É o valor do estorno menos o custo do gateway
refundable_balance Quanto ainda pode ser estornado nesta cobrança

Erros

HTTP Código Quando
404 not_found A cobrança não existe, é de outra empresa, ou é de outro ambiente
422 not_paid_yet A cobrança ainda está pendente. Um PIX não escaneado não tem o que estornar
422 already_refunded Já foi estornada integralmente
422 exceeds_refundable_balance Valor acima do saldo. A mensagem traz o saldo disponível
422 invalid_amount Valor zero ou negativo
422 refund_declined O gateway recusou. O motivo dele vem em details. Um Pix pago há poucos segundos ainda não pode ser estornado: o Asaas pede para tentar de novo em alguns instantes, e passados um ou dois minutos o mesmo pedido funciona
O webhook também avisa. O estorno que zera o saldo da venda dispara payment.refunded na hora, junto da resposta síncrona, e não sai de novo quando o gateway confirmar pelo lado dele. Estorno feito fora da API, pelo painel ou pelo gateway, também chega por ele. Estorno parcial não dispara webhook: a venda continua paid. No cartão em real o estorno é imediato, e funciona no sandbox.

Gateways: o que você não precisa fazer

No modelo padrão você não abre conta, não configura webhook e não guarda credencial em gateway nenhum. A N7 Pay opera as contas de adquirência e cuida de toda essa camada; a sua integração termina na API do N7.

Existe um segundo modelo para quem já tem contrato próprio com uma adquirente: a conta continua sendo sua, o dinheiro não passa pela N7, e a N7 orquestra roteamento, conciliação e webhooks por cima dela. Nesse caso a credencial é cadastrada uma vez no painel, fica guardada cifrada, e a cobrança falha com 424 missing_merchant_credentials enquanto ela não existir. Não há queda silenciosa para a conta da N7: uma venda sua nunca é processada na conta de outro.

Qual dos dois vale é definido por gateway, não por integração, e a resposta da cobrança não muda em nenhum dos casos. Se você não pediu o segundo, está no primeiro.

Responsabilidade De quem é
Conta no gateway N7 Pay
Webhook do gateway N7 Pay
Credenciais de adquirência N7 Pay, ou você quando usa conta própria
Conciliação de eventos N7 Pay
Receber webhooks de saída do N7 Você

Quando o pagamento é confirmado na ponta, o N7 normaliza o evento e entrega para você num formato único, o mesmo payload independente de qual gateway processou. É o que está descrito em Webhooks de saída, e é o único que você precisa consumir.

Smart Routing

O Smart Routing seleciona automaticamente o melhor gateway para cada transação com base em:

Prioridade por empresa: Cada empresa pode ter gateways atribuídos com prioridade customizada e override de custo. O Smart Routing respeita essas configurações por cima dos defaults globais.

Critérios de roteamento por método

Método Gateway principal Fallback
pix Asaas (melhor custo BRL) Outro gateway com suporte PIX
boleto Asaas não se aplica
card_1x · card_2_6x · card_7_12x Asaas (BRL) / Stripe (USD, EUR) Gateway com maior aprovação 24h
usd / wire Stripe não se aplica

Split de Receita

Com o split ativo, o valor líquido de cada cobrança, ou seja, o total menos a taxa N7 conforme o seu plano de tarifas, cai direto na sua carteira, e só a taxa fica retida pela N7. Não há repasse manual nem espera por transferência.

Nada muda na sua integração: você não envia parâmetro nenhum a mais. O net_amount devolvido em cada cobrança é exatamente o que vai para você.

Cobrança nunca falha por causa do split. Enquanto a sua carteira não estiver provisionada, a cobrança é criada normalmente e o valor fica retido na N7 para repasse. Assim que o provisionamento conclui, as cobranças seguintes já saem divididas automaticamente, sem mudança do seu lado.

O provisionamento da sua carteira é feito pela N7 durante o onboarding, a partir dos dados cadastrais da sua empresa. Se o split ainda não estiver ativo na sua conta, fale com o time N7, porque não há nada a configurar por API.

Em que moeda você recebe

A resposta depende da rota, e não da moeda em que você precificou. O que decide é a moeda em que a cobrança sai, porque é ela que o pagador paga e é ela que entra.

Rota Cobrança sai em Você é liquidado em Como o dinheiro chega
POST /charges com currency: "BRL" BRL BRL Split automático: o net_amount cai na sua carteira na adquirente. Sem carteira provisionada, fica retido na N7 para repasse
POST /charges/international BRL BRL Igual à linha acima: é uma venda em real como qualquer outra. Você precificou em dólar, o pagador pagou um PIX em reais, e nenhum dólar se moveu. É por isso que o fx.settlement_currency vem BRL
POST /charges com currency: "USD" ou "EUR" USD / EUR USD / EUR Sem split automático. O valor cai integralmente na conta da N7 na adquirente internacional, e o repasse é combinado com o time N7. Não há carteira sua do outro lado
Venda em dólar não vira real sozinha. Se você quer precificar em dólar e receber em real, a rota é POST /charges/international: ela trava a cotação, cobra um PIX em reais e liquida em reais. Já POST /charges com currency: "USD" cobra o pagador em dólar de verdade e deixa você com saldo em dólar, que não é convertido nem repassado automaticamente. São operações diferentes, e a escolha da rota é a escolha da moeda em que você recebe.

O saldo é por moeda, nunca somado: uma venda de US$ 50 entra no saldo em dólar, e não como 50 no saldo em reais. O painel mostra um cartão por moeda. Não existe endpoint de saldo nem de saque na API: consulte o painel, ou some o net_amount das transações paid por currency em GET /transactions.

Split Payment (CBS e IBS)

Não confunda com o split de receita acima. Aquele divide o dinheiro entre você e a N7. Este separa o tributo da reforma tributária no momento da liquidação e o encaminha para a Receita Federal (CBS) e para o Comitê Gestor do IBS.

Em construção, com contrato de API já definido. A especificação abaixo segue o Manual de Operações do Split Payment publicado pelo CGIBS em versão preliminar. Os campos e eventos estão desenhados e serão liberados primeiro em sandbox. Enquanto isso, enviar o bloco fiscal retorna 422 com split_payment_unavailable, sem quebrar a cobrança.

Como funciona

Preencher os campos fiscais é o próprio ato de optar pelo split. Se você não enviar o bloco fiscal, a cobrança segue exatamente como hoje. Se enviar, o CBS e o IBS são separados do valor pago antes de o líquido chegar até você.

Escopo por método

Método Arranjo Split
pix Pix dinâmico na primeira fase
boleto Boleto na primeira fase
card_1x, card_2_6x, card_7_12x Cartão fora da primeira fase
usd / wire / cripto Stripe e cripto fora do escopo

Enviar fiscal em um método fora de escopo retorna 422 com split_not_eligible_for_method.

Parâmetros de POST /charges, bloco fiscal

Campo Tipo Obrigatoriedade Descrição
fiscal.cbs decimal obrigatório com ibs Valor de CBS informado. Zero é válido
fiscal.ibs decimal obrigatório com cbs Valor de IBS informado. Zero é válido
fiscal.documento_fiscal string opcional Chave de acesso do documento fiscal. Pode ser vinculada depois
fiscal.recebedor_cnpj string obrigatório CNPJ de quem recebe. É sempre o seu, nunca o da N7
A N7 não valida o tributo, e isso é por definição. O manual atribui a apuração ao contribuinte. Validamos apenas duas regras de forma: nenhum valor negativo, e a soma de CBS e IBS não pode superar o valor da cobrança. Se alguma falhar, a resposta é 422 com split_rejected_syntactic antes de a cobrança chegar ao gateway.

Campos da resposta

Campo Descrição
fiscal.cbs_informado O que você declarou na criação
fiscal.cbs_corrigido Valor que o governo devolveu quando há divergência com o documento fiscal
fiscal.cbs_em_aberto Teto do que ainda pode ser recolhido, quando parte do débito já foi extinta
fiscal.cbs_segregado O que efetivamente saiu na liquidação
fiscal.valor_original Valor da cobrança, sem multa e sem juros
fiscal.valor_pago Valor efetivamente pago
fiscal.valor_liquido Valor pago menos CBS e IBS segregados, antes da taxa N7
fiscal.txid Identificador do arranjo, usado na conciliação

Os campos de IBS seguem exatamente a mesma estrutura. CBS e IBS são calculados de forma independente e nunca se compensam entre si.

Como o valor segregado é calculado

Em boleto, Pix dinâmico e Pix automático o valor pago pode diferir do valor original, então entra proporcionalidade:

segregado = mín( (valor_pago / valor_original) * C , C , A )

C = cbs_corrigido quando existir, senão cbs_informado
A = cbs_em_aberto quando existir

# truncado para baixo em duas casas, sem arredondamento estatístico
# R$ 12,35954 vira R$ 12,35

Em Pix estático, TED e TEF não há proporcionalidade: o segregado é igual ao informado.

Eventos de webhook

Evento Quando dispara
split.super_inteligente.corrected O governo corrigiu o valor do tributo antes da liquidação
split.super_inteligente.em_aberto O teto a recolher foi reduzido porque parte do débito já foi extinta
split.segregated O tributo foi separado. É o evento que explica por que o líquido caiu
split.remitted O repasse ao governo foi confirmado, em lote e por arranjo
payment.under_review A liquidação foi retida por análise de risco e o tributo ainda não foi separado
Estorno comercial não devolve o tributo. Se você estorna uma venda com split, o CBS e o IBS já foram encaminhados ao governo. O canal de ocorrência do manual cobre apenas incidente de segurança e falha operacional de instituição, nunca desacordo comercial ou desistência do comprador. A recuperação de valor pago a maior é feita pela Receita e pelo Comitê Gestor diretamente ao fornecedor, em até três dias úteis, fora do fluxo de pagamento. A resposta de estorno da N7 informa quanto do valor já virou tributo.
Antecipação trabalha sobre o líquido. Antecipar recebível não antecipa o tributo. Se você usa D+0 em uma cobrança com split, a N7 antecipa sobre o valor já descontado de CBS e IBS, com margem extra para a hipótese de correção pelo governo antes da liquidação.

Suporte Internacional

O N7 Pay suporta clientes e cobranças globais. Cada gateway tem requisitos diferentes:

Campo do cliente Asaas (PIX/Boleto) Stripe (Cartão)
name obrigatório obrigatório
document (CPF/CNPJ) obrigatório não usado
email recomendado obrigatório
address_line1 opcional obrigatório
country BR implícito obrigatório
zip_code opcional obrigatório
Recomendação: Sempre capture todos os campos ao registrar o cliente. O N7 Pay armazena tudo e usa apenas o que cada gateway exige, sem interromper o fluxo se campos opcionais estiverem ausentes.

Exemplos cURL

Listar links

curl https://n7pay.com/api/v1/payment-links \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui"

Criar link USD (Stripe)

curl -X POST https://n7pay.com/api/v1/payment-links \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "International Plan",
    "amount": "29.00",
    "currency": "USD",
    "methods": ["card_1x"]
  }'

Consultar transação

curl https://n7pay.com/api/v1/transactions/142 \
  -H "Authorization: Bearer n7_sk_sand_SuaChaveAqui"

Exemplos JavaScript / TypeScript

Criar link de pagamento

const res = await fetch("https://n7pay.com/api/v1/payment-links", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.N7_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Plano Mensal",
    amount: "99.90",
    currency: "BRL",
    methods: ["pix"],
  }),
});
const link = await res.json();
console.log(link.url); // https://n7pay.com/pay/abc123

Verificar assinatura de webhook

import crypto from "node:crypto";

// signature: o header X-N7-Signature ("sha256=..."); timestamp: o X-N7-Timestamp.
// O valor assinado é "{timestamp}.{corpo cru}", nunca o corpo sozinho.
function verifyWebhook(
  rawBody: Buffer,
  signature: string,
  timestamp: string,
  secret: string
): boolean {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest("hex");

  const received = signature.replace(/^sha256=/, "");
  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Suporte: Dúvidas ou problemas? Entre em contato pelo dashboard ou envie e-mail para suporte@n7pay.com.