NxGate Transaction API
Cobre por PIX — e em outras moedas pela v2 —, faça saques via PIX, consulte o saldo e receba webhooks assinados quando cada transação mudar de status. Uma API REST/JSON, autenticada pela sua api_key.
Chave, primeiro PIX e webhook em três passos.
Outras moedasCobre em pesos mexicanos e nas próximas moedas pela v2.
Integre com IAContexto da API e prompts prontos para colar na sua IA.
https://api.nxgate.com.brEm 3 passos
Integrar é direto: você cria a cobrança, mostra o código PIX ao cliente e confirma o pagamento quando o webhook chegar.
- Pegue a api_key — crie em Painel → API e guarde no servidor, numa variável de ambiente.
- Gere o PIX — chame
POST /v2/cobrancacomapi_key,valorewebhook. Mostre opaymentCode(copia-e-cola) e o QR Code, e guarde oidTransaction. - Receba o webhook e confirme — quando chegar o
QR_CODE_COPY_AND_PASTE_PAID, confirme naPOST /v2/consultare libere o pedido só compaid.
Passo a passo com código em Comece aqui. Já usa a /pix/gerar? Ela continua funcionando — a v2 aceita o mesmo corpo e devolve os mesmos campos, com alguns a mais.
curl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"valor": 1.00,
"webhook": "https://seusite.com.br/webhook/nxgate",
"magic_id": "pedido-teste-1"
}'import { randomUUID } from "node:crypto";
const resposta = await fetch("https://api.nxgate.com.br/v2/cobranca", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Idempotency-Key": randomUUID(), // guarde com o pedido
},
body: JSON.stringify({
api_key: process.env.NXGATE_API_KEY, // só no servidor
valor: 1.00,
webhook: "https://seusite.com.br/webhook/nxgate",
magic_id: "pedido-teste-1",
}),
});
const cobranca = await resposta.json();
if (cobranca.status !== "success") throw new Error(cobranca.error || cobranca.message);
console.log(cobranca.idTransaction, cobranca.paymentCode);<?php
$ch = curl_init('https://api.nxgate.com.br/v2/cobranca');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Idempotency-Key: ' . bin2hex(random_bytes(16)), // guarde com o pedido
],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'), // só no servidor
'valor' => 1.00,
'webhook' => 'https://seusite.com.br/webhook/nxgate',
'magic_id' => 'pedido-teste-1',
]),
]);
$cobranca = json_decode((string) curl_exec($ch), true);
curl_close($ch);
if (($cobranca['status'] ?? '') !== 'success') {
throw new RuntimeException($cobranca['error'] ?? $cobranca['message'] ?? 'Falha na cobrança');
}
echo $cobranca['idTransaction'], ' ', $cobranca['paymentCode'];import os
import uuid
import requests
resposta = requests.post(
"https://api.nxgate.com.br/v2/cobranca",
headers={"X-Idempotency-Key": str(uuid.uuid4())}, # guarde com o pedido
json={
"api_key": os.environ["NXGATE_API_KEY"], # só no servidor
"valor": 1.00,
"webhook": "https://seusite.com.br/webhook/nxgate",
"magic_id": "pedido-teste-1",
},
timeout=30,
)
cobranca = resposta.json()
if cobranca.get("status") != "success":
raise RuntimeError(cobranca.get("error") or cobranca.get("message"))
print(cobranca["idTransaction"], cobranca["paymentCode"])Comece aqui
Do zero ao primeiro PIX pago em três passos. Os exemplos usam a v2 (POST /v2/cobranca), que serve para PIX e para as outras moedas.
1. Pegue a sua api_key
No painel da NxGate, abra API (nxgate.com.br/api) e crie uma chave. A chave simples tem acesso total; na chave avançada você escolhe o escopo — para um checkout, use cashin, que gera e consulta PIX mas não saca. Veja os escopos.
- Guarde a chave só no servidor, numa variável de ambiente. A API recusa chamadas feitas do navegador (CORS) e a chave não pode aparecer no front-end, no app nem no repositório.
- Na mesma tela ficam o Segredo de assinatura de webhook (HMAC), usado no passo 3 (se ele não aparecer, veja qual é o segredo), e o IP Seguro para Saques, obrigatório se você for sacar ou consultar o saldo pela API.
# .env do seu servidor (nunca no front-end nem no Git)
NXGATE_API_KEY=nx_live_sua_chave_aqui
NXGATE_WEBHOOK_SECRET=seu_segredo_de_assinatura2. Gere o PIX
Chame POST /v2/cobranca com a api_key, o valor em reais e a URL do seu webhook. Sem moeda, a cobrança é PIX em BRL. Mande também um magic_id com o ID do pedido e o header X-Idempotency-Key, que evita cobrança duplicada se a rede oscilar e você repetir a chamada.
curl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"valor": 1.00,
"webhook": "https://seusite.com.br/webhook/nxgate",
"magic_id": "pedido-teste-1"
}'import { randomUUID } from "node:crypto";
const resposta = await fetch("https://api.nxgate.com.br/v2/cobranca", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Idempotency-Key": randomUUID(), // guarde com o pedido
},
body: JSON.stringify({
api_key: process.env.NXGATE_API_KEY, // só no servidor
valor: 1.00,
webhook: "https://seusite.com.br/webhook/nxgate",
magic_id: "pedido-teste-1",
}),
});
const cobranca = await resposta.json();
if (cobranca.status !== "success") throw new Error(cobranca.error || cobranca.message);
console.log(cobranca.idTransaction, cobranca.paymentCode);<?php
$ch = curl_init('https://api.nxgate.com.br/v2/cobranca');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Idempotency-Key: ' . bin2hex(random_bytes(16)), // guarde com o pedido
],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'), // só no servidor
'valor' => 1.00,
'webhook' => 'https://seusite.com.br/webhook/nxgate',
'magic_id' => 'pedido-teste-1',
]),
]);
$cobranca = json_decode((string) curl_exec($ch), true);
curl_close($ch);
if (($cobranca['status'] ?? '') !== 'success') {
throw new RuntimeException($cobranca['error'] ?? $cobranca['message'] ?? 'Falha na cobrança');
}
echo $cobranca['idTransaction'], ' ', $cobranca['paymentCode'];import os
import uuid
import requests
resposta = requests.post(
"https://api.nxgate.com.br/v2/cobranca",
headers={"X-Idempotency-Key": str(uuid.uuid4())}, # guarde com o pedido
json={
"api_key": os.environ["NXGATE_API_KEY"], # só no servidor
"valor": 1.00,
"webhook": "https://seusite.com.br/webhook/nxgate",
"magic_id": "pedido-teste-1",
},
timeout=30,
)
cobranca = resposta.json()
if cobranca.get("status") != "success":
raise RuntimeError(cobranca.get("error") or cobranca.get("message"))
print(cobranca["idTransaction"], cobranca["paymentCode"])Na resposta:
paymentCode— o PIX copia-e-cola. Mostre com um botão de copiar e gere o QR Code a partir dele (qualquer biblioteca de QR Code serve).idTransaction— guarde no pedido. É ele que volta no webhook (data.tx_id) e que você usa na consulta.paymentCodeBase64— conforme a rede de pagamento que processou a cobrança, vem a imagem PNG do QR em base64 ou o próprio copia-e-cola em base64. Por isso, gere o QR pelopaymentCode.
{
"status": "success",
"message": "ok",
"paymentCode": "00020101021226850014br.gov.bcb.pix...6304A1B2",
"idTransaction": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"paymentCodeBase64": "iVBORw0KGgoAAAANSUhEUgAAAQ4AAAEOCAYAAAB4sfmn...",
"currency": "BRL",
"paymentMethod": "PIX",
"paymentInstruction": {
"type": "pix",
"pixCopyAndPaste": "00020101021226850014br.gov.bcb.pix...6304A1B2"
}
}3. Receba o webhook e confirme
Quando o cliente paga, a NxGate faz um POST na sua URL com type: QR_CODE_COPY_AND_PASTE_PAID. Confira a assinatura, responda 2xx rápido e, antes de liberar o pedido, confirme na POST /v2/consultar: libere só quando vier "status": "paid".
import express from "express";
import crypto from "node:crypto";
const app = express();
const API = "https://api.nxgate.com.br";
const API_KEY = process.env.NXGATE_API_KEY; // só no servidor
const SEGREDO = process.env.NXGATE_WEBHOOK_SECRET; // Painel → API → segredo de assinatura
function assinaturaValida(corpo, ts, assinatura) {
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const esperado = "sha256=" + crypto.createHmac("sha256", SEGREDO)
.update(ts + ".").update(corpo).digest("hex");
const a = Buffer.from(assinatura || ""), b = Buffer.from(esperado);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// express.raw: a assinatura é conferida sobre o corpo BRUTO
app.post("/webhook/nxgate", express.raw({ type: "*/*" }), async (req, res) => {
if (!assinaturaValida(req.body, req.get("X-NXGate-Timestamp") || "", req.get("X-NXGate-Signature"))) {
return res.sendStatus(401);
}
const evento = JSON.parse(req.body.toString("utf8"));
res.sendStatus(200); // responda rápido; o resto roda em seguida
try {
if (evento.type === "QR_CODE_COPY_AND_PASTE_PAID") {
// confirme na API antes de liberar
const r = await fetch(`${API}/v2/consultar`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_key: API_KEY, transaction_id: evento.data.tx_id }),
});
const consulta = await r.json();
if (consulta.status === "paid") await marcarPedidoPago(evento.data.tx_id);
} else if (["PIX_CASHOUT_PAID", "PIX_CASHOUT_ERROR", "PIX_CASHOUT_REFUND"].includes(evento.type)) {
await atualizarSaque(evento.idTransaction, evento.type);
}
// status "pending" (sem type) e eventos desconhecidos: só ignore
} catch (erro) {
console.error("webhook nxgate", erro); // a reconciliação cobre o que falhar aqui
}
});
async function marcarPedidoPago(txId) { /* só se o pedido ainda não estiver pago */ }
async function atualizarSaque(idTransaction, tipo) { /* idempotente */ }
app.listen(3000);<?php
// webhook.php — recebe os webhooks da NxGate
$corpo = file_get_contents('php://input'); // corpo BRUTO
$ts = $_SERVER['HTTP_X_NXGATE_TIMESTAMP'] ?? '';
$assinatura = $_SERVER['HTTP_X_NXGATE_SIGNATURE'] ?? '';
$esperado = 'sha256=' . hash_hmac('sha256', $ts . '.' . $corpo, (string) getenv('NXGATE_WEBHOOK_SECRET'));
if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300 || !hash_equals($esperado, $assinatura)) {
http_response_code(401);
exit;
}
$evento = json_decode($corpo, true);
$tipo = $evento['type'] ?? '';
if ($tipo === 'QR_CODE_COPY_AND_PASTE_PAID') {
// confirme na API antes de liberar
$txId = $evento['data']['tx_id'];
$ch = curl_init('https://api.nxgate.com.br/v2/consultar');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'),
'transaction_id' => $txId,
]),
]);
$consulta = json_decode((string) curl_exec($ch), true);
curl_close($ch);
if (($consulta['status'] ?? '') === 'paid') {
marcarPedidoPago($txId);
}
} elseif (in_array($tipo, ['PIX_CASHOUT_PAID', 'PIX_CASHOUT_ERROR', 'PIX_CASHOUT_REFUND'], true)) {
atualizarSaque($evento['idTransaction'], $tipo);
}
// status "pending" (sem type) e eventos desconhecidos: só ignore
http_response_code(200);
function marcarPedidoPago(string $txId): void { /* só se o pedido ainda não estiver pago */ }
function atualizarSaque(string $idTransaction, string $tipo): void { /* idempotente */ }import hashlib
import hmac
import json
import os
import time
import httpx
from fastapi import FastAPI, Request, Response
app = FastAPI()
API = "https://api.nxgate.com.br"
API_KEY = os.environ["NXGATE_API_KEY"] # só no servidor
SEGREDO = os.environ["NXGATE_WEBHOOK_SECRET"].encode() # Painel → API
def assinatura_valida(corpo: bytes, ts: str, assinatura: str) -> bool:
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
return False
esperado = "sha256=" + hmac.new(SEGREDO, ts.encode() + b"." + corpo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, assinatura)
def marcar_pedido_pago(tx_id: str) -> None:
... # só se o pedido ainda não estiver pago
def atualizar_saque(id_transaction: str, tipo: str) -> None:
... # idempotente
@app.post("/webhook/nxgate")
async def webhook(request: Request):
corpo = await request.body() # corpo BRUTO
if not assinatura_valida(corpo, request.headers.get("x-nxgate-timestamp", ""),
request.headers.get("x-nxgate-signature", "")):
return Response(status_code=401)
evento = json.loads(corpo)
tipo = evento.get("type")
if tipo == "QR_CODE_COPY_AND_PASTE_PAID":
tx_id = evento["data"]["tx_id"]
async with httpx.AsyncClient(timeout=10) as http: # confirme na API antes de liberar
r = await http.post(f"{API}/v2/consultar",
json={"api_key": API_KEY, "transaction_id": tx_id})
if r.json().get("status") == "paid":
marcar_pedido_pago(tx_id)
elif tipo in ("PIX_CASHOUT_PAID", "PIX_CASHOUT_ERROR", "PIX_CASHOUT_REFUND"):
atualizar_saque(evento["idTransaction"], tipo)
# status "pending" (sem type) e eventos desconhecidos: só ignore
return Response(status_code=200)O webhook não é reenviado automaticamente se o seu servidor falhar. Tenha uma rotina que consulte os pedidos pendentes de tempos em tempos e use Painel → Webhooks para ver e reenviar entregas.
Quer testar antes sem dinheiro de verdade? Use o sandbox: mesma API, chave de teste, e você simula pagamento, expiração, estorno e saque.
Testou com R$ 1,00 e viu paid na consulta? Passe pelo checklist antes de ir pra produção — e, se quiser que uma IA escreva a integração no seu stack, use os prompts prontos.
Autenticação
Toda chamada leva a chave no corpo JSON, no campo api_key — não existe header de autenticação. A conta dona da chave precisa estar aprovada.
| Campo | Onde | Descrição |
|---|---|---|
api_key | body | Sua chave secreta. Chaves novas começam com nx_live_. Nunca exponha no front-end. |
Escopos da chave
Crie até 5 chaves ativas em Painel → API. Use uma chave por integração, com o menor escopo possível: se uma vazar, revogue só ela — a revogação vale na hora.
| Escopo | Libera |
|---|---|
full | Tudo: cobrança, consulta, saque e saldo (é o da chave simples). |
cashin | /v2/cobranca, /v2/consultar, /pix/gerar e /pix/consultar. |
cashout | /pix/sacar. |
saldo | /saldo. |
A api_key é um segredo: use só no servidor. Se vazar, revogue em Painel → API e crie outra. A chave marcada como Interna (usada pelo checkout e pelo PixLink da NxGate) não pode ser revogada pela tela.
Erros de autenticação
| Status | Resposta | Quando ocorre |
|---|---|---|
| 401 | {"error": "api_key obrigatoria"} | Corpo sem api_key. |
| 401 | {"error": "API Key invalida"} | Chave inexistente. |
| 401 | {"error": "API Key revogada ou inativa"} | Chave revogada. |
| 403 | {"error": "Conta nao liberada"} | Conta ainda não aprovada. |
| 403 | {"error": "esta API Key nao tem permissao de geracao de PIX"} | O escopo da chave não cobre a rota (também ...de saque e ...de consulta de saldo). |
{
"api_key": "nx_live_sua_chave_aqui",
"valor": 150.5
}Base URL & Ambientes
Todas as rotas usam a mesma base, só em HTTPS. Para testar sem dinheiro de verdade, use o sandbox (https://sandbox.nxgate.com.br, com a chave de teste). Depois, faça um teste real em produção com valor baixo — o mínimo de uma cobrança PIX é R$ 1,00.
| Ambiente | URL | Descrição |
|---|---|---|
| Produção | https://api.nxgate.com.br | Transações reais. |
| Sandbox | https://sandbox.nxgate.com.br | Testes sem dinheiro de verdade, com a chave de teste nxg_test_…. Veja Sandbox. |
Formato das requisições
- Todas as rotas são
POST, com corpo JSON e headerContent-Type: application/json. - Corpo de até 1 MB.
- Chame a API do seu servidor: requisições de navegador vindas de outros sites são bloqueadas (CORS).
- Valores em dinheiro são decimais na moeda (
150.50= R$ 150,50). A exceção é o split fixo, que é em centavos.
Idempotência
Envie o header X-Idempotency-Key com um UUID v4 novo em cada cobrança ou saque. Se a mesma chave chegar de novo dentro do prazo, a API devolve a resposta da primeira vez em vez de criar outra cobrança ou outro saque.
| Rota | Prazo da chave |
|---|---|
POST /v2/cobranca | 5 minutos |
POST /pix/gerar | 5 minutos |
POST /pix/sacar | 10 minutos |
Como a API trata a chave
| Situação | O que acontece |
|---|---|
| Primeira requisição com a chave | Processa normalmente. |
| Mesma chave, e a primeira já respondeu HTTP 200 | Devolve a mesma resposta (mesmo idTransaction ou internalreference). Nada novo é criado. |
| Mesma chave enquanto a primeira ainda processa | 409 {"status": "error", "message": "Requisicao com esta X-Idempotency-Key ainda esta sendo processada. Aguarde a resposta da requisicao original."} |
| A primeira cobrança falhou | Em /v2/cobranca e /pix/gerar a chave fica presa por até 60 s (responde 409). Para tentar de novo logo, use uma chave nova. |
| O primeiro saque falhou | Em /pix/sacar a chave é liberada na hora e pode ser reusada. Exceção: Saldo insuficiente fica guardado como resposta por 10 min — depois de pôr saldo, use uma chave nova. |
A chave vale para a API inteira, não só para a sua conta ou rota: gere um UUID v4 novo por operação, guarde junto do pedido ou do saque e reuse só para repetir aquela mesma operação.
X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000Rate Limiting
Os limites são por minuto, por API key e por IP de origem. Passou do limite, a API responde 429 Too Many Requests.
| Rota | Por API key | Por IP |
|---|---|---|
/v2/cobranca, /pix/gerar | 10.000/min | 5.000/min |
/v2/consultar, /pix/consultar | — | 10.000/min |
/pix/sacar | 100/min | 60/min |
/saldo | 60/min | 120/min |
| Qualquer rota (limite geral) | — | 20.000/min |
O contador por IP é um só para todas as rotas, e cada chamada soma 2 nele (o limite geral e o da rota). Exemplo: um IP que só gera cobranças chega aos 5.000 com 2.500 chamadas no minuto — e um servidor que gera muitas cobranças pode esbarrar no limite por IP do saque (60) ou do saldo (120). Se acontecer, espere o X-RateLimit-Reset ou faça saque e saldo de outro IP liberado.
Na consulta, depois de 50 falhas de autenticação (api_key inválida ou revogada) do mesmo IP em 5 minutos, as consultas desse IP respondem 429 até a janela acabar. Consultas com chave válida não contam.
Plataforma com muitos clientes saindo do mesmo IP, ou volume acima destes limites? Fale com o seu gerente: os limites podem ser ajustados.
Headers de resposta
| Header | Descrição |
|---|---|
X-RateLimit-Limit | Limite da janela (o último limite aplicado na rota). |
X-RateLimit-Remaining | Requisições restantes (0 quando bloqueado). |
X-RateLimit-Reset | Quando o contador por IP zera (Unix, em segundos). |
Respostas 429
| Limite | Corpo |
|---|---|
| Por IP | {"error": "Too many requests. Tente novamente em breve."} |
| Por API key | {"error": "Limite de requisicoes excedido para esta API Key."} |
| Falhas de autenticação na consulta | {"error": "Muitas tentativas. Aguarde."} |
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1791244860
{"error": "Too many requests. Tente novamente em breve."}Erros
A API usa os códigos HTTP padrão. O corpo de erro vem em um de dois formatos — trate os dois: {"error": "..."} ou {"status": "error", "message": "..."}. Erros de validação da cobrança trazem também detalhes.
| Status | Significado | Quando ocorre |
|---|---|---|
| 400 | Requisição inválida | JSON malformado; na consulta, api_key ou transaction_id ausentes. |
| 401 | Não autorizado | api_key ausente, inválida ou revogada. |
| 403 | Acesso negado | Conta não liberada, escopo da chave, geração ou saque desabilitado, IP não liberado. |
| 404 | Não encontrado | Transação inexistente ou de outra conta; rota inexistente ({"error": "Endpoint nao encontrado"}). |
| 409 | Conflito | X-Idempotency-Key ainda em processamento; no /saldo, conta sem processador configurado. |
| 422 | Dados inválidos | Corpo fora do formato ou regra de negócio (valor, documento, split, moeda...). |
| 429 | Muitas requisições | Rate limit. |
| 500 | Erro interno | Falha inesperada ou a rede de pagamento não gerou a cobrança. |
| 503 | Indisponível | Saques temporariamente indisponíveis. |
No POST /pix/sacar, validação e saldo insuficiente voltam com HTTP 200 e "status": "error". Confira sempre status === "success" antes de considerar a operação aceita.
Exemplo de erro de validação (422)
{
"error": "Dados invalidos",
"detalhes": [
{
"code": "custom",
"message": "Para split fixo, \"value\" deve ser um inteiro de centavos maior que zero (ex.: 500 = R$ 5,00)",
"path": [
"split_users",
0
]
}
]
}As mensagens vêm em português sem acento (ex.: "Valor invalido"). Use o status HTTP para a lógica e a mensagem só como apoio. Os erros de cada rota estão na página dela.
Limites
| Restrição | Valor |
|---|---|
| Cobrança PIX (BRL) — mínimo | R$ 1,00 |
| Cobrança PIX (BRL) — máximo | R$ 100.000,00 por cobrança, ou o limite de cobrança da sua conta, se for menor |
| Saque — mínimo | R$ 1,00 (o valor líquido também precisa ficar em R$ 1,00 ou mais) |
| Saque — máximo | R$ 50.000,00 por saque, ou o limite de saque da sua conta, se for menor |
| Split por cobrança | até 90% do valor (50% abaixo de R$ 2,00) |
| Outras moedas | veja valores por moeda |
| Corpo da requisição | até 1 MB |
| Campos de texto | até 255 caracteres (celular: até 20) |
Teto do split: a soma das entradas de split (percentuais + fixas) pode repassar no máximo 90% do valor; os outros 10% ficam com a conta que gerou a cobrança. Num PIX de R$ 100,00, dá para repassar até R$ 90,00. Abaixo de R$ 2,00 o teto é 50% (num PIX de R$ 1,00, no máximo R$ 0,50). Passou do teto, 422.
Acima do limite da sua conta, a API responde 422 com o limite na mensagem: {"status": "error", "message": "Valor excede o limite de cashin (R$ 500.00)."} — no saque, limite de saque. Para mudar um limite, fale com o seu gerente.
Pagamento em análise: um PIX pago acima do limite de liberação automática da sua conta fica retido para análise. O webhook PIX Pago é enviado normalmente, o valor vai para o saldo congelado e a consulta responde waiting até a liberação. Combine esse limite com o seu gerente.
Sandbox (ambiente de teste)
Uma cópia isolada da API para testar a integração inteira sem dinheiro de verdade: cobrança em BRL, MXN, ARS, COP e BOB, consulta, webhooks, saldo e saque.
Rotas, respostas, erros e webhooks são os mesmos da produção — mesmo payload, mesmos headers e a mesma assinatura. Mudam só o endereço e a chave.
| Ambiente | URL | Chave |
|---|---|---|
| Sandbox | https://sandbox.nxgate.com.br | Chave de teste nxg_test_… — gere no painel, em API → Sandbox. |
| Produção | https://api.nxgate.com.br | A sua chave normal. |
Uma chave não funciona no ambiente da outra: a chave de teste recebe 401 {"error": "Chave de teste: use https://sandbox.nxgate.com.br"} na produção, e a chave de produção recebe 401 no sandbox.
Rotas
| Rota | O que faz |
|---|---|
POST /v2/cobranca e POST /pix/gerar | Cobrança em BRL e nas outras moedas, com as mesmas validações da produção. |
POST /v2/consultar e POST /pix/consultar | Consulta da cobrança (paid_at em UTC). |
POST /pix/sacar e POST /saldo | Saque e saldo do sandbox. |
POST /v2/sandbox/simular | Só no sandbox. Simula o que a rede de pagamento faria — veja Simular um resultado. |
GET /pagar/{{idTransaction}} | Só no sandbox. Página de pagamento de teste, com os botões “Simular pagamento” e “Expirar”. |
O que muda em relação à produção
- A instrução de pagamento é fictícia, no formato certo e impossível de pagar: PIX copia-e-cola (BRL), CLABE de 18 dígitos (MXN), CVU de 22 dígitos com titular “NxGate Sandbox” (ARS), link para a página de teste (COP) e QR (BOB). Não tente pagar.
- Nada acontece sozinho: a cobrança fica aguardando e o saque fica processando até você simular.
/saldoe/pix/sacarnão exigem IP liberado no sandbox. Na produção exigem: libere o IP do servidor no painel.- No sandbox, o segredo da assinatura
X-NXGate-Signatureé a própria chave de teste. - Os webhooks saem pelo mesmo caminho dos da produção: o IP de origem é da Cloudflare. Valide pela assinatura, não pelo IP.
- Os dados de teste são apagados depois de 30 dias.
Limites do sandbox
| O quê | Limite |
|---|---|
| Por IP (todas as rotas) | 30 por minuto |
| Por chave de teste (todas as rotas somadas) | 20 por minuto |
Cobrança (/v2/cobranca + /pix/gerar) | 5 por minuto |
Simulação (/v2/sandbox/simular + botões do painel e da página /pagar) | 5 por minuto por conta |
Saque (/pix/sacar) | 2 por minuto |
Consulta e saldo (/v2/consultar + /pix/consultar + /saldo, somados) | 10 por minuto |
| Teto diário por chave de teste | 200 operações (zera à meia-noite de Brasília) |
Passou do limite: 429 {"error": "Limite do sandbox excedido: …"} com o header Retry-After (segundos até poder tentar de novo). Os limites da produção são outros, bem maiores — não use o sandbox para teste de carga.
Simular um resultado
POST /v2/sandbox/simular com api_key, transaction_id (o idTransaction da cobrança ou o internalreference do saque) e resultado. Também dá pra simular pelos botões do painel, em API → Sandbox.
| Transação | resultado | Webhook (igual ao da produção) |
|---|---|---|
| Cobrança | pago | QR_CODE_COPY_AND_PASTE_PAID — nas outras moedas, com currency e payment_method. |
| Cobrança | expirado | Nenhum. A cobrança não pode mais ser paga. |
| Cobrança | disputa | Nenhum. O valor da venda fica congelado no saldo. |
| Cobrança | estornado | Nenhum. O valor sai do saldo. |
| Saque | pago | PIX_CASHOUT_PAID |
| Saque | erro | PIX_CASHOUT_ERROR; o valor volta para o saldo. motivo opcional: generico, chave_invalida, documento, recusado ou indisponivel. |
| Saque | estornado | PIX_CASHOUT_REFUND; o valor volta para o saldo. |
Ao criar a cobrança também chega o webhook com "status": "pending", como na produção. A simulação responde 200 com "webhook": "disparado" | "sem_url" | "nao_se_aplica"; 404 se a transação não é da sua chave; 409 se a mudança não vale (ex.: pagar uma cobrança expirada); 422 se o resultado não existe.
Indo para a produção
- Troque a URL base para
https://api.nxgate.com.bre a chave de teste pela sua chave de produção — deixe as duas em variáveis de ambiente. - Libere o IP do servidor no painel: é obrigatório para
/pix/sacare/saldo. - Tire as chamadas a
/v2/sandbox/simular: na produção, quem confirma é o pagamento de verdade. - Faça um teste real com valor baixo e passe pelo checklist.
Vai integrar com IA? Os prompts prontos já pedem para começar pelo sandbox.
# 1) cobrança em MXN → volta a CLABE de teste
curl -X POST https://sandbox.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-d '{
"api_key": "nxg_test_...",
"moeda": "MXN",
"valor": 100,
"email_pagador": "[email protected]",
"webhook": "https://seu-sistema.com/webhook/nxgate"
}'
# 2) simula o pagamento → chega QR_CODE_COPY_AND_PASTE_PAID
curl -X POST https://sandbox.nxgate.com.br/v2/sandbox/simular \
-H "Content-Type: application/json" \
-d '{"api_key": "nxg_test_...", "transaction_id": "<idTransaction>", "resultado": "pago"}'
# 3) consulta → "status": "paid" e paid_at em UTC
curl -X POST https://sandbox.nxgate.com.br/v2/consultar \
-H "Content-Type: application/json" \
-d '{"api_key": "nxg_test_...", "transaction_id": "<idTransaction>"}'{
"api_key": "nxg_test_...",
"transaction_id": "<idTransaction ou internalreference>",
"resultado": "pago"
}Antes de ir pra produção
Repasse esta lista com quem vai colocar a integração no ar.
- api_key só no servidorEm variável de ambiente; nunca no front-end, no app, em log ou no Git. Use a chave com o menor escopo (por exemplo,
cashinpara checkout). - Webhook em HTTPS públicoURL acessível da internet, respondendo 2xx em menos de 12 s. Trabalho pesado vai para fila.
- Pagamento confirmado pela consultaAntes de liberar o produto,
POST /v2/consultar(ou/pix/consultar) precisa responderpaid. Webhook sozinho não basta. - Assinatura validada (1 conta)Confira
X-NXGate-Signaturecom o segredo HMAC — veja Assinatura. Plataforma multi-cliente não valida HMAC: confirma só pela consulta, com a api_key do cliente. - Idempotência dos dois lados
X-Idempotency-Key(UUID v4) em toda cobrança e saque; o webhook é processado de forma idempotente, porque pode chegar de novo num reenvio. - IDs guardados
idTransactionemagic_idno pedido;internalreferenceno saque. - IP liberado para saque e saldoIP do servidor em Painel → API → IP Seguro para Saques. Se o servidor sair por IPv6, cadastre o IPv6 ou force IPv4.
- Erros tratados401, 403, 409, 422, 429 e 5xx — e o
"status": "error"com HTTP 200 do saque. - ReconciliaçãoO webhook não é reenviado sozinho: tenha uma rotina que consulta as cobranças pendentes.
- QR Code pelo paymentCodeGere a imagem a partir do copia-e-cola; o
paymentCodeBase64muda de formato conforme a rede. - Testado no sandbox e com R$ 1,00Rode o fluxo inteiro no sandbox (pago, expirado, estorno, saque com erro) e depois um teste real em produção: gere, pague, receba o webhook e veja
paidna consulta antes de abrir para os clientes.
Quer que a IA confira o seu código contra esta lista? Cole o Contexto da API para IA e peça uma revisão.
Cobrança em outras moedas
A v2 cobra em reais e em outras moedas com o mesmo jeito de integrar da /pix/gerar: o mesmo corpo com dois campos a mais (moeda e metodo) e a mesma resposta com campos a mais. Em BRL ela é a /pix/gerar — mesma taxa, mesmas regras.
MXN, ARS, COP e BOB não dá pra pagar de verdade num teste de integração: use o sandbox e simule o pagamento — o webhook chega igual ao da produção.
| Moeda | metodo | paymentInstruction.type | Status |
|---|---|---|---|
| BRL · Real brasileiro | PIX (padrão) | pix | Disponível |
| MXN · Peso mexicano | SPEI (padrão) · OXXO em breve | clabe | Em breve |
| ARS · Peso argentino | BANK_TRANSFER (padrão) | cvu | Em breve |
| COP · Peso colombiano | BREB · NEQUI em breve | qr | Em breve |
| BOB · Boliviano | QR (padrão) | qr | Em breve |
Como o dinheiro chega: em outras moedas, a taxa da NxGate é cobrada na moeda da venda (percentual + valor fixo — veja em Perfil → Taxas em …). O líquido fica como aguardando conversão e é convertido para real na cotação do dia, no prazo de recebimento da sua conta (veja em Perfil); depois da conversão, entra no seu saldo em R$. O saque é sempre em real, pelo POST /pix/sacar.
Use só as moedas marcadas como Disponível. Moeda ainda não liberada responde 422 com Moeda ARS ainda nao disponivel. Moeda estrangeira também exige conta aprovada e CPF/CNPJ do titular válido.
Gerar cobrança em qualquer moeda
Cria a cobrança e devolve como o comprador paga. Sem moeda (ou com "moeda": "BRL") é o PIX de sempre: a chamada passa pela mesma regra da /pix/gerar — mesmos limites, validações, taxa, split, magic_id, webhook e erros.
Rate limit: 10.000/min por API key e 5.000/min por IP (detalhes). Idempotência: header X-Idempotency-Key, prazo de 5 minutos.
Headers
| Header | Descrição |
|---|---|
| Content-Typeobrigatório | application/json |
| X-Idempotency-Keyopcional | UUID v4 novo por cobrança (recomendado). Como funciona. |
Parâmetros do corpo
| Campo | Descrição |
|---|---|
| api_keystringobrigatório | Chave com escopo full ou cashin. |
| valornumberobrigatório | Valor decimal na moeda da cobrança (150.50 = R$ 150,50; em MXN, 499.90 = MX$ 499,90). Não é em centavos. Em BRL, de 1.00 a 100000.00. |
| moedastringopcional | BRL (padrão), MXN, ARS, COP ou BOB — veja o status de cada moeda. |
| metodostringopcional | Meio de pagamento. BRL: PIX. MXN: SPEI. ARS: BANK_TRANSFER. COP: BREB (envie sempre; NEQUI em breve). BOB: QR. Sem ele, vale o padrão da moeda. |
| nome_pagadorstringopcional | Nome do pagador (até 255 caracteres). |
| documento_pagadorstringopcional | BRL: CPF (11 dígitos) ou CNPJ (14), com ou sem pontuação; sem ele, a API usa o CPF/CNPJ da sua conta. COP e BOB: documento do pagador no país (obrigatório). MXN e ARS: não é usado. |
| email_pagadorstringopcional | E-mail do pagador. Obrigatório em COP e BOB. |
| celularstringopcional | Telefone do pagador (até 20 caracteres). Obrigatório em COP e BOB. |
| descricaostringopcional | Descrição do pagamento (até 255). |
| webhookstring (url)opcional | URL pública que recebe os webhooks desta cobrança — http ou https (prefira HTTPS), na porta 80 a 443 ou de 3000 em diante. localhost e IPs privados são recusados. |
| magic_idstringopcional | Seu identificador (ex.: ID do pedido; até 255). Volta no webhook de pago em data.magic_id. |
| split_usersarrayopcional | Divide o valor com outras contas NxGate — veja Split de pagamento. |
Resposta 200 OK
| Campo | Descrição |
|---|---|
| statusstring | "success" |
| messagestring | "ok" |
| paymentCodestring | null | O que o comprador usa para pagar: PIX copia-e-cola, CLABE, CVU, link ou código QR. null enquanto a instrução é gerada. |
| idTransactionstring | ID da cobrança. Guarde: é o transaction_id da /v2/consultar e o data.tx_id do webhook de pago. Trate como texto — o formato varia. |
| paymentCodeBase64string | null | BRL: imagem PNG do QR em base64 ou o próprio copia-e-cola em base64, conforme a rede que processou a cobrança — gere o QR pelo paymentCode. Outras moedas: null. |
| currencystring | Moeda da cobrança ("BRL", "MXN"...). |
| paymentMethodstring | Meio de pagamento ("PIX", "SPEI"...). |
| paymentInstructionobject | Como o comprador paga, por tipo — veja paymentInstruction. |
Split de pagamento
Use split_users para repassar parte do valor a outras contas NxGate. Cada item é { "username", "type", "value" }:
type: "percentage"—valueé o percentual do valor (20= 20%).type: "fixed"—valueé fixo em centavos da moeda, inteiro e maior que zero (500= R$ 5,00).
{
"split_users": [
{
"username": "revendedor1",
"type": "percentage",
"value": 20
},
{
"username": "parceiro_fixo",
"type": "fixed",
"value": 500
}
]
}O formato antigo { "username", "percentage" } continua aceito.
Pode misturar percentual e fixo. O total não passa de 90% do valor (50% abaixo de R$ 2,00 — ou de 2 unidades da moeda); acima disso, 422. A taxa da NxGate sai da sua parte. Em moeda estrangeira, se taxa + split passarem do valor, a cobrança é recusada.
Confira os usernames. Em BRL, a parte de um username que não existe é descontada do seu líquido e não vai para ninguém; em outras moedas, ela fica com você.
Se a sua conta tiver um split configurado pela NxGate, ele soma ao da API quando o total couber no teto; se não couber, vale só o da API.
Erros
Em BRL, exatamente os erros da /pix/gerar. Em outras moedas, os da tabela Erros e limites (v2). Autenticação, rate limit e idempotência: Erros.
curl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 6f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"valor": 150.50,
"nome_pagador": "João Silva",
"documento_pagador": "12345678901",
"webhook": "https://seusite.com.br/webhook/nxgate",
"magic_id": "pedido-123"
}'import { randomUUID } from "node:crypto";
const resposta = await fetch("https://api.nxgate.com.br/v2/cobranca", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Idempotency-Key": randomUUID(), // reuse só para repetir esta mesma operação
},
body: JSON.stringify({
api_key: process.env.NXGATE_API_KEY,
valor: 150.50,
nome_pagador: "João Silva",
documento_pagador: "12345678901",
webhook: "https://seusite.com.br/webhook/nxgate",
magic_id: "pedido-123",
}),
});
const dados = await resposta.json();
if (dados.status !== "success") throw new Error(dados.error || dados.message);
console.log(dados.idTransaction); // guarde no pedido
console.log(dados.paymentCode); // copia-e-cola: mostre e gere o QR a partir dele<?php
$ch = curl_init('https://api.nxgate.com.br/v2/cobranca');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Idempotency-Key: ' . bin2hex(random_bytes(16)), // reuse só para repetir esta operação
],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'),
'valor' => 150.50,
'nome_pagador' => 'João Silva',
'documento_pagador' => '12345678901',
'webhook' => 'https://seusite.com.br/webhook/nxgate',
'magic_id' => 'pedido-123',
]),
]);
$dados = json_decode((string) curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http !== 200 || ($dados['status'] ?? '') !== 'success') {
throw new RuntimeException($dados['error'] ?? $dados['message'] ?? "HTTP $http");
}
echo $dados['idTransaction']; // guarde no pedido
echo $dados['paymentCode']; // copia-e-cola: mostre e gere o QR a partir deleimport os
import uuid
import requests
resposta = requests.post(
"https://api.nxgate.com.br/v2/cobranca",
headers={"X-Idempotency-Key": str(uuid.uuid4())}, # reuse só para repetir esta operação
json={
"api_key": os.environ["NXGATE_API_KEY"],
"valor": 150.50,
"nome_pagador": "João Silva",
"documento_pagador": "12345678901",
"webhook": "https://seusite.com.br/webhook/nxgate",
"magic_id": "pedido-123",
},
timeout=30,
)
dados = resposta.json()
if resposta.status_code != 200 or dados.get("status") != "success":
raise RuntimeError(dados.get("error") or dados.get("message"))
print(dados["idTransaction"]) # guarde no pedido
print(dados["paymentCode"]) # copia-e-cola: mostre e gere o QR a partir delecurl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"moeda": "MXN",
"metodo": "SPEI",
"valor": 499.90,
"nome_pagador": "Juan Pérez",
"email_pagador": "[email protected]",
"descricao": "Curso X",
"webhook": "https://seusite.com.br/webhook/nxgate"
}'{
"status": "success",
"message": "ok",
"paymentCode": "00020101021226850014br.gov.bcb.pix...6304A1B2",
"idTransaction": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"paymentCodeBase64": "iVBORw0KGgoAAAANSUhEUgAAAQ4AAAEOCAYAAAB4sfmn...",
"currency": "BRL",
"paymentMethod": "PIX",
"paymentInstruction": {
"type": "pix",
"pixCopyAndPaste": "00020101021226850014br.gov.bcb.pix...6304A1B2"
}
}{
"status": "success",
"message": "ok",
"paymentCode": "646180157000000004",
"idTransaction": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"paymentCodeBase64": null,
"currency": "MXN",
"paymentMethod": "SPEI",
"paymentInstruction": {
"type": "clabe",
"clabe": "646180157000000004"
}
}{
"status": "success",
"message": "ok",
"paymentCode": null,
"idTransaction": "2c9d8e7f-6a5b-4c3d-9e1f-0a1b2c3d4e5f",
"paymentCodeBase64": null,
"currency": "ARS",
"paymentMethod": "BANK_TRANSFER",
"paymentInstruction": {
"type": "pendente"
}
}paymentInstruction
Diz como o comprador paga. O campo type define o que mais vem no objeto; o paymentCode da resposta traz sempre o dado principal (o mesmo que você mostraria num botão “copiar”).
type | Moeda · método | Campos | O que mostrar ao comprador |
|---|---|---|---|
pix | BRL · PIX | pixCopyAndPaste (na /v2/consultar: pix_copy_and_paste) | O copia-e-cola e o QR Code gerado a partir dele. |
clabe | MXN · SPEI | clabe | A CLABE de 18 dígitos, com botão de copiar, e o valor exato. Confirma em segundos. |
oxxo | MXN · OXXO Em breve | reference, barcodeUrl | Referência e código de barras para pagar em dinheiro numa loja OXXO. |
cvu | ARS · BANK_TRANSFER | cvu, beneficiary, bankName | O CVU, o nome do titular (beneficiary) e o banco/carteira (bankName) — bancos e apps de remessa pedem esses dados. Titular e banco podem vir null nos primeiros segundos; a /v2/consultar completa. |
redirect | COP · NEQUI Em breve | url | Leve o comprador para a url (página do banco ou da carteira). |
qr | BOB · QR · COP · BREB | qr | Código no padrão EMV (como o PIX): gere o QR a partir dele e mostre também o código. |
pendente | ARS, COP e BOB | — | A instrução ainda está sendo gerada (a API espera até ~8 s). paymentCode vem null: consulte a /v2/consultar a cada poucos segundos e use o payment_instruction dela. |
Trate type desconhecido de forma genérica (mostre o paymentCode): novos meios de pagamento podem chegar sem mudar a versão da API.
Os nomes mudam entre a criação e a consulta: na /v2/cobranca é paymentInstruction com pixCopyAndPaste; na /v2/consultar é payment_instruction com pix_copy_and_paste. Os demais campos (clabe, cvu, beneficiary, bankName, url, qr, reference, barcodeUrl) têm o mesmo nome nos dois.
Consultar cobrança em qualquer moeda
Mesmo corpo, mesmos limites e mesmos erros da /pix/consultar. A resposta traz status e transaction_id e mais os detalhes da cobrança — inclusive a instrução de pagamento enquanto ela não foi paga.
Parâmetros do corpo
| Campo | Descrição |
|---|---|
| api_keystringobrigatório | Chave com escopo full ou cashin. A cobrança precisa ser da conta dessa chave. |
| transaction_idstringobrigatório | O idTransaction devolvido pela /v2/cobranca ou pela /pix/gerar. Em BRL, também aceita o transaction_id do webhook PIX Pendente. |
Resposta 200 OK
| Campo | Descrição |
|---|---|
| statusstring | "paid" ou "waiting". |
| transaction_idstring | Eco do identificador consultado. |
| currencystring | Moeda da cobrança. |
| amountnumber | Valor cobrado, na moeda da cobrança. |
| payment_methodstring | Meio de pagamento (PIX, SPEI...). |
| paid_atstring | null | Data e hora da confirmação em ISO 8601, sempre em UTC (o Z no fim), em qualquer moeda; null enquanto não pago. |
| payment_instructionobject | null | A instrução da cobrança enquanto waiting; null depois de paga. |
| amount_brlnumber | null | Só em outras moedas: quanto a venda virou em reais no seu saldo (o seu líquido, já convertido). null enquanto a conversão para reais não acontece e sempre em BRL. |
| converted_atstring | null | Data e hora da conversão para reais, em ISO 8601 UTC; null enquanto não converteu. |
Só paid confirma. waiting quer dizer que o pagamento ainda não foi confirmado: aguardando pagamento ou em análise. Se você recebeu o webhook de pago e a consulta continua em waiting, o pagamento está em análise — não peça para o cliente pagar de novo e consulte mais tarde.
Erros
| Status | Resposta | Quando ocorre |
|---|---|---|
| 400 | {"error": "api_key e transaction_id sao obrigatorios"} | Faltou um dos campos. |
| 401 | {"error": "API Key invalida"} | Chave inexistente (ou API Key revogada ou inativa). |
| 403 | {"error": "esta API Key nao tem permissao de geracao de PIX"} | Chave sem escopo cashin. |
| 403 | {"error": "Usuario nao vinculado"} | Chave sem conta vinculada. |
| 404 | {"error": "Transacao nao encontrada"} | Não existe ou é de outra conta. |
| 429 | {"error": "Muitas tentativas. Aguarde."} | Muitas falhas de autenticação do mesmo IP (detalhes). |
curl -X POST https://api.nxgate.com.br/v2/consultar \
-H "Content-Type: application/json" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90"
}'const resposta = await fetch("https://api.nxgate.com.br/v2/consultar", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
api_key: process.env.NXGATE_API_KEY,
transaction_id: "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
}),
});
const dados = await resposta.json();
if (!resposta.ok) throw new Error(dados.error || dados.message);
if (dados.status === "paid") {
// pagamento confirmado: libere o pedido (uma vez só)
}<?php
$ch = curl_init('https://api.nxgate.com.br/v2/consultar');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'),
'transaction_id' => 'e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90',
]),
]);
$dados = json_decode((string) curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http !== 200) {
throw new RuntimeException($dados['error'] ?? "HTTP $http");
}
if ($dados['status'] === 'paid') {
// pagamento confirmado: libere o pedido (uma vez só)
}import os
import requests
resposta = requests.post(
"https://api.nxgate.com.br/v2/consultar",
json={
"api_key": os.environ["NXGATE_API_KEY"],
"transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
},
timeout=30,
)
dados = resposta.json()
if resposta.status_code != 200:
raise RuntimeError(dados.get("error"))
if dados["status"] == "paid":
pass # pagamento confirmado: libere o pedido (uma vez só){
"status": "paid",
"transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"currency": "BRL",
"amount": 150.5,
"payment_method": "PIX",
"paid_at": "2026-10-05T12:30:00.000Z",
"payment_instruction": null
}{
"status": "waiting",
"transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"currency": "BRL",
"amount": 150.5,
"payment_method": "PIX",
"paid_at": null,
"payment_instruction": {
"type": "pix",
"pix_copy_and_paste": "00020101021226850014br.gov.bcb.pix...6304A1B2"
}
}{
"status": "waiting",
"transaction_id": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"currency": "MXN",
"amount": 499.9,
"payment_method": "SPEI",
"paid_at": null,
"payment_instruction": {
"type": "clabe",
"clabe": "646180157000000004"
}
}{
"status": "paid",
"transaction_id": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"currency": "MXN",
"amount": 499.9,
"payment_method": "SPEI",
"paid_at": "2026-10-05T15:30:00.000Z",
"payment_instruction": null,
"amount_brl": 116.62,
"converted_at": "2026-10-06T00:00:00.710Z"
}Webhook em outras moedas
É o mesmo webhook de hoje — PIX Pendente e PIX Pago (QR_CODE_COPY_AND_PASTE_PAID) —, com o mesmo formato, os mesmos headers, a mesma assinatura e o mesmo jeito de identificar a transação. Nas cobranças em moeda estrangeira vêm dois campos a mais:
| Campo | Descrição |
|---|---|
| currencystring | Moeda da cobrança (ex.: "MXN"). No PIX Pendente vem na raiz; no PIX Pago, em data.currency. |
| payment_methodstring | Meio de pagamento (ex.: "SPEI"). No PIX Pendente vem na raiz; no PIX Pago, em data.payment_method. |
amount/data.amountvêm na moeda da cobrança (decimal, como no BRL).data.tx_id,data.tagedata.qr_code_id= oidTransactionda/v2/cobranca; no PIX Pendente,transaction_idtambém é oidTransaction.- Campos que só existem no PIX (copia-e-cola, agência/conta, tipo de documento...) vêm
null.data.end_to_endtraz a referência do pagamento na rede local, quando houver. - Cobranças em BRL não mudam nada: continuam sem
currency. Regra simples no seu código:moeda = data.currency ?? "BRL".
Quem já trata o QR_CODE_COPY_AND_PASTE_PAID não precisa mudar nada para receber as outras moedas — só passar a ler data.currency para separar os valores por moeda.
{
"type": "QR_CODE_COPY_AND_PASTE_PAID",
"data": {
"amount": 499.9,
"amount_chargeback": 0,
"base_64_image": null,
"base_64_image_url": null,
"account_number": null,
"agency_number": null,
"debtor_document": null,
"debtor_institution": null,
"debtor_institution_number": null,
"debtor_name": "Juan Pérez",
"due_date": null,
"end_to_end": "MBAN01002610050012345678",
"expiration_date": null,
"fee": 0,
"fine": null,
"fine_date": null,
"payment_date": "2026-10-05T15:30:00.000Z",
"pix_copy_and_paste": null,
"qr_code_id": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"status": "PAID",
"tag": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"tx_id": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"type_document": null,
"type_fine": "NONE",
"worked": true,
"magic_id": null,
"currency": "MXN",
"payment_method": "SPEI"
}
}{
"status": "pending",
"transaction_id": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"amount": 499.9,
"message": "Cobranca gerada, aguardando pagamento",
"adquirente": "nxgate",
"currency": "MXN",
"payment_method": "SPEI"
}Venda convertida (gateways)
Para quem opera como gateway e repassa as vendas para sellers próprios. Vendas em moeda estrangeira entram no seu saldo em reais depois da conversão; este webhook avisa, venda por venda, quanto cada uma virou em reais — o valor que você usa para creditar o seu seller.
Liberado por conta. Fale com o suporte para ativar. Contas sem a liberação continuam recebendo só o PIX Pago.
- Vai para a mesma URL de webhook da cobrança, com os mesmos headers e a mesma assinatura.
- Chega depois do
QR_CODE_COPY_AND_PASTE_PAIDda mesma venda — 1 webhook por venda. typepróprio:CURRENCY_CONVERTED, comdata.status: "CONVERTED". Não é um novo pagamento: não libere o pedido de novo — trate pelotype.- Case por
data.tx_id(=idTransaction), como no PIX Pago. Para conferir, a /v2/consultar devolve o mesmoamount_brl.
| Campo | Descrição |
|---|---|
| typestring | "CURRENCY_CONVERTED" |
| data.statusstring | "CONVERTED" |
| data.tx_idstring | O idTransaction da cobrança. data.tag e data.qr_code_id trazem o mesmo valor. |
| data.magic_idstring | null | O seu magic_id, se enviado na cobrança. |
| data.currencystring | Moeda da venda (ex.: "MXN"). |
| data.payment_methodstring | Meio de pagamento (ex.: "SPEI"). |
| data.amountnumber | Valor da venda, na moeda. |
| data.net_amountnumber | O seu líquido na moeda (venda − taxa). |
| data.amount_brlnumber | Quanto entrou no seu saldo em reais por esta venda. |
| data.exchange_ratenumber | Reais por 1 unidade da moeda aplicados na conversão (amount_brl ≈ net_amount × exchange_rate, arredondado para baixo). |
| data.converted_atstring | Data e hora da conversão, ISO 8601 em UTC. |
| data.conversion_idstring | Identificador da conversão — várias vendas convertidas juntas trazem o mesmo. |
Quem não trata CURRENCY_CONVERTED só precisa responder 200 e ignorar — nada muda no fluxo do PIX Pago.
{
"type": "CURRENCY_CONVERTED",
"data": {
"status": "CONVERTED",
"tx_id": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"tag": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"qr_code_id": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"magic_id": "pedido-123",
"currency": "MXN",
"payment_method": "SPEI",
"amount": 499.9,
"net_amount": 444.91,
"amount_brl": 116.62,
"exchange_rate": 0.26213,
"converted_at": "2026-10-06T00:00:00.710Z",
"conversion_id": "17927568013432561575774559"
}
}Erros e limites da v2
Em BRL, a /v2/cobranca devolve exatamente os erros da /pix/gerar (e os de autenticação, rate limit e idempotência). Em outras moedas, além desses:
| Status | Resposta | Quando ocorre |
|---|---|---|
| 422 | {"error": "Moeda invalida"} | moeda não é um código de 3 letras. |
| 422 | {"error": "Moeda nao suportada: XYZ"} | Moeda fora da lista (BRL, MXN, ARS, COP, BOB). |
| 422 | {"error": "Moeda ARS ainda nao disponivel"} | Moeda ainda não liberada. |
| 422 | {"error": "Metodo OXXO nao disponivel para MXN"} | Método inexistente ou desligado na moeda (em BRL: Metodo X nao disponivel para BRL). |
| 403 | {"status": "error", "message": "Conta nao aprovada para cobranca internacional."} | Conta ainda não aprovada. |
| 403 | {"status": "error", "message": "Geracao de PIX desabilitada para esta conta."} | Geração de PIX desabilitada na conta (só em BRL, igual à /pix/gerar). |
| 403 | {"status": "error", "message": "Cobranca em MXN desabilitada para esta conta."} | Cobrança naquela moeda desabilitada na conta (cada moeda é liberada separadamente). |
| 422 | {"error": "Documento do titular da conta invalido para cobranca internacional"} | O CPF/CNPJ do dono da conta não é válido (ele é o responsável pela cobrança). |
| 422 | {"error": "Valor invalido"} | valor ausente, zero ou negativo. |
| 422 | {"error": "Valor minimo por transacao e 20.00 MXN"} | Abaixo do mínimo da moeda (valores por moeda). |
| 422 | {"status": "error", "message": "Valor excede o limite de cashin (MXN 5000.00)."} | Acima do máximo por cobrança na moeda. |
| 422 | {"error": "Valor maximo por transacao excedido"} | Acima do máximo da rede (ARS, COP e BOB). |
| 422 | {"error": "Valor menor que a taxa da transacao"} | O valor não cobre a taxa (percentual + fixo). |
| 422 | {"error": "Split e taxa excedem o valor da transacao"} | Taxa + split passam do valor. |
| 422 | {"error": "Split excede o teto de 90% do valor (maximo MXN 449.91)"} | Split acima do teto. |
| 422 | {"error": "Telefone do pagador obrigatorio"} | COP e BOB exigem os dados do pagador (também Documento do pagador obrigatorio e E-mail do pagador obrigatorio). |
| 422 | {"error": "URL de webhook invalida ou nao permitida"} | Webhook inválido (mesma regra da /pix/gerar). |
| 422 | {"error": "Cobranca indisponivel para este valor e metodo"} | A rede não aceitou cobrar esse valor nesse método. Tente outro valor ou método. |
| 500 | {"error": "Falha ao gerar cobranca"} | A rede de pagamento recusou ou não respondeu. Gere uma nova cobrança (com nova X-Idempotency-Key) — a que falhou não tem instrução de pagamento. |
Valor mínimo e máximo de cada moeda: Valores e limites.
Métodos de pagamento por moeda
Cada moeda tem os seus meios de pagamento. É sempre o mesmo endpoint — POST /v2/cobranca — mudando moeda e, quando a moeda tem mais de um meio, metodo. A resposta e o webhook seguem o padrão de hoje, com currency, paymentMethod e paymentInstruction a mais.
| Moeda | Meio | metodo | Instrução (paymentInstruction.type) | Quando chega | Status |
|---|---|---|---|---|---|
| BRL | PIX | "PIX" (padrão) | pix — copia-e-cola + QR | na resposta | Disponível |
| MXN | SPEI | "SPEI" (padrão) | clabe | na resposta | Em breve |
| MXN | OXXO | "OXXO" | oxxo — referência + código de barras | na resposta | Em breve |
| ARS | Transferência (CVU) | "BANK_TRANSFER" (padrão) | cvu — CVU + titular + banco | alguns segundos | Em breve |
| COP | Nequi | "NEQUI" | redirect (link) | alguns segundos | Em breve |
| COP | Bre-B | "BREB" | qr (copia-e-cola, estilo PIX) | na resposta | Em breve |
| BOB | QR | "QR" (padrão) | qr | alguns segundos | Em breve |
Sem metodo, vale o padrão da moeda. O valor é sempre decimal, na moeda da cobrança: "valor": 150.50 em MXN são MX$ 150,50 — não é em centavos.
Saque é sempre em R$. O que entra em MXN, ARS, COP ou BOB é convertido para real na cotação do dia, no prazo de recebimento da sua conta (veja em Perfil), e cai no seu saldo para sacar pelo fluxo normal (/pix/sacar). Você não pede a conversão nem aprova cotação. Enquanto não converte, o valor aparece como aguardando conversão no painel e ainda não pode ser sacado.
A diferença que mais quebra integração
Nem toda moeda entrega a instrução de pagamento na mesma hora:
- Na resposta — BRL e MXN. Você cria a cobrança e já mostra o código na tela.
- Alguns segundos depois — ARS, COP e BOB. A API espera até ~8 s; se a instrução ainda não estiver pronta, a resposta vem com
paymentInstruction.type: "pendente"epaymentCode: null. Consulte a /v2/consultar a cada poucos segundos até opayment_instructionvir preenchido.
Se a sua tela assume que o código sempre vem na criação, ela quebra no primeiro país que demora.
Dados do pagador exigidos
Cada rede pede um conjunto diferente. Faltando um obrigatório, a API recusa com 422 antes de criar a cobrança.
| Moeda | nome_pagador | email_pagador | celular | documento_pagador |
|---|---|---|---|---|
| BRL | opcional | opcional | opcional | opcional — sem ele, vale o CPF/CNPJ da sua conta |
| MXN | recomendado | opcional | opcional | não usado |
| ARS | recomendado | opcional | opcional | não usado |
| COP | recomendado | obrigatório | obrigatório (com ou sem o DDI 57) | obrigatório — CC, NIT, CE ou TI |
| BOB | recomendado | obrigatório | obrigatório (com ou sem o DDI 591) | obrigatório — CI, NIT ou PAS |
Mande sempre o nome real do pagador: sem ele, a rede recebe um nome genérico.
Valores e limites
| Moeda | Mínimo por transação | Máximo por transação |
|---|---|---|
| BRL | R$ 1,00 | R$ 100.000,00 |
| MXN | MX$ 20,00 | MX$ 5.000,00 |
| ARS | AR$ 2.000,00 | AR$ 1.000.000,00 |
| COP | COL$ 10.000,00 | COL$ 500.000,00 |
| BOB | Bs 10,00 | Bs 10.000,00 |
Fora da faixa, a API responde 422 com o limite na mensagem — veja os erros da v2.
Brasil (BRL) Disponível
Em reais, a /v2/cobranca é a /pix/gerar: sem moeda (ou com "moeda": "BRL") o corpo, as validações, a taxa, o split, o webhook e os erros são exatamente os mesmos. A resposta traz os campos de sempre e mais currency, paymentMethod e paymentInstruction.
Quem já integra a /pix/gerar não precisa mudar nada: a rota antiga continua funcionando. Para integrações novas, use a v2 — a mesma integração serve para as outras moedas.
Gere o QR Code a partir do paymentCode. O paymentCodeBase64 vem como imagem PNG em base64 ou como o próprio copia-e-cola em base64, conforme a rede de pagamento que processou a cobrança.
# sem "moeda" (ou com "moeda": "BRL") = PIX, igual à /pix/gerar
curl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"moeda": "BRL",
"valor": 150.50,
"nome_pagador": "João Silva",
"documento_pagador": "12345678901",
"webhook": "https://seusite.com.br/webhook/nxgate"
}'{
"status": "success",
"message": "ok",
"paymentCode": "00020101021226850014br.gov.bcb.pix...6304A1B2",
"idTransaction": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"paymentCodeBase64": "iVBORw0KGgoAAAANSUhEUgAAAQ4AAAEOCAYAAAB4sfmn...",
"currency": "BRL",
"paymentMethod": "PIX",
"paymentInstruction": {
"type": "pix",
"pixCopyAndPaste": "00020101021226850014br.gov.bcb.pix...6304A1B2"
}
}México (MXN) Em breve
O meio de pagamento é o SPEI — a transferência bancária instantânea do México, o “PIX mexicano”. O comprador transfere pelo app do banco dele para uma CLABE de 18 dígitos que a API devolve na própria resposta. Confirma em segundos, pelo mesmo webhook de venda paga. Valor por cobrança: de MX$ 20,00 a MX$ 5.000,00.
metodo | O que é | Instrução | Confirmação |
|---|---|---|---|
"SPEI" (padrão) | transferência bancária instantânea | clabe na resposta | segundos |
"OXXO" Em breve | dinheiro no balcão da loja | reference + código de barras | horas ou dias |
curl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"moeda": "MXN",
"metodo": "SPEI",
"valor": 499.90,
"nome_pagador": "Juan Pérez",
"email_pagador": "[email protected]",
"descricao": "Curso X",
"webhook": "https://seusite.com.br/webhook/nxgate"
}'{
"status": "success",
"message": "ok",
"paymentCode": "646180157000000004",
"idTransaction": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5",
"paymentCodeBase64": null,
"currency": "MXN",
"paymentMethod": "SPEI",
"paymentInstruction": {
"type": "clabe",
"clabe": "646180157000000004"
}
}Mostre a CLABE com um botão de copiar e o valor exato: o comprador cola no app do banco e transfere. Transferência com valor diferente do cobrado não é creditada automaticamente.
Argentina (ARS) Em breve
O meio de pagamento é a transferência para um CVU (a conta virtual argentina, de 22 dígitos), de qualquer banco ou carteira do país — Mercado Pago, Ualá, Brubank e outros. O CVU fica pronto alguns segundos depois da criação: veja a diferença que mais quebra integração.
metodo | O que é | Instrução | Confirmação |
|---|---|---|---|
"BANK_TRANSFER" (padrão) | transferência para CVU | cvu, beneficiary (titular) e bankName (banco/carteira) | segundos depois da transferência |
Mostre ao comprador o CVU, o nome do titular e o banco/carteira: bancos e apps de remessa pedem esses dados para transferir. Valor por cobrança: de AR$ 2.000 a AR$ 1.000.000.
curl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f7a" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"moeda": "ARS",
"valor": 15000.00,
"nome_pagador": "Martín García",
"email_pagador": "[email protected]",
"descricao": "Curso X",
"webhook": "https://seusite.com.br/webhook/nxgate"
}'{
"status": "success",
"message": "ok",
"paymentCode": null,
"idTransaction": "2c9d8e7f-6a5b-4c3d-9e1f-0a1b2c3d4e5f",
"paymentCodeBase64": null,
"currency": "ARS",
"paymentMethod": "BANK_TRANSFER",
"paymentInstruction": {
"type": "pendente"
}
}{
"status": "success",
"message": "ok",
"paymentCode": "0000076500000012345678",
"idTransaction": "2c9d8e7f-6a5b-4c3d-9e1f-0a1b2c3d4e5f",
"paymentCodeBase64": null,
"currency": "ARS",
"paymentMethod": "BANK_TRANSFER",
"paymentInstruction": {
"type": "cvu",
"cvu": "0000076500000012345678",
"beneficiary": "Nombre Apellido",
"bankName": "Nombre del banco"
}
}Colômbia (COP) Em breve
O meio disponível é o Bre-B, o pagamento instantâneo do banco central (o “PIX colombiano”): envie "metodo": "BREB". A resposta traz um código copia-e-cola no padrão EMV, o mesmo do PIX (type: "qr"): gere a imagem do QR a partir dele e mostre os dois. O comprador escaneia no app de qualquer banco ou carteira colombiana (Nequi, Daviplata, Bancolombia…) e o pagamento cai em segundos.
type: "redirect"— usado pelo Nequi (Em breve): um link (url) para o comprador pagar no app.
metodo | O que é |
|---|---|
"BREB" | Bre-B, pagamento instantâneo (QR copia-e-cola) |
"NEQUI" Em breve | carteira digital Nequi |
O metodo é opcional em COP: sem ele, a cobrança sai em Bre-B (padrão). Métodos fora da lista respondem 422.
Dica que aumenta a conversão no Bre-B: mostre o QR com um botão para baixar a imagem, junto do copia-e-cola. Muita gente compra e paga no mesmo celular: o comprador salva o QR, abre o app do banco (Nequi, Daviplata, Bancolombia…) e lê a imagem direto da galeria, sem precisar de outro aparelho pra escanear.
A Colômbia exige celular (com ou sem o DDI 57), documento_pagador (CC, NIT, CE ou TI) e email_pagador — sem qualquer um deles, 422. Mande também o nome_pagador real. Valor por cobrança: de COL$ 10.000 a COL$ 500.000 — e como o valor é decimal, "valor": 10000 são dez mil pesos.
curl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8c" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"moeda": "COP",
"metodo": "BREB",
"valor": 50000,
"nome_pagador": "Carlos Gómez",
"email_pagador": "[email protected]",
"celular": "3001234567",
"documento_pagador": "1020304050",
"webhook": "https://seusite.com.br/webhook/nxgate"
}'{
"status": "success",
"message": "ok",
"paymentCode": "00020101021226400015CO.COM.VISI.LLA…6304A1B2",
"idTransaction": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"paymentCodeBase64": null,
"currency": "COP",
"paymentMethod": "BREB",
"paymentInstruction": {
"type": "qr",
"qr": "00020101021226400015CO.COM.VISI.LLA…6304A1B2"
}
}Bolívia (BOB) Em breve
O meio de pagamento é o QR interbancário boliviano: a API devolve um código (qr) alguns segundos depois da criação. Gere a imagem do QR a partir dele e mostre ao comprador, que paga escaneando no app do banco.
metodo | O que é | Instrução |
|---|---|---|
"QR" (padrão) | QR interbancário | qr |
A Bolívia exige celular (com ou sem o DDI 591), documento_pagador (CI, NIT ou PAS) e email_pagador — sem qualquer um deles, 422. Mande também o nome_pagador real. Valor por cobrança: de Bs 10 a Bs 10.000.
curl -X POST https://api.nxgate.com.br/v2/cobranca \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9d" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"moeda": "BOB",
"valor": 100.00,
"nome_pagador": "Ana Quispe",
"email_pagador": "[email protected]",
"celular": "70012345",
"documento_pagador": "1234567",
"webhook": "https://seusite.com.br/webhook/nxgate"
}'{
"status": "success",
"message": "ok",
"paymentCode": "0002010102122663...6304B7C1",
"idTransaction": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"paymentCodeBase64": null,
"currency": "BOB",
"paymentMethod": "QR",
"paymentInstruction": {
"type": "qr",
"qr": "0002010102122663...6304B7C1"
}
}Gerar QR Code PIX
A rota de sempre para cobrar em PIX — continua funcionando igual. Para integrações novas, prefira a /v2/cobranca: ela aceita o mesmo corpo e devolve os mesmos campos (mais currency, paymentMethod e paymentInstruction).
Rate limit: 10.000/min por API key e 5.000/min por IP. Idempotência: X-Idempotency-Key, prazo de 5 minutos.
Headers
| Header | Descrição |
|---|---|
| Content-Typeobrigatório | application/json |
| X-Idempotency-Keyopcional | UUID v4 novo por cobrança (recomendado). |
Parâmetros do corpo
| Campo | Descrição |
|---|---|
| api_keystringobrigatório | Chave com escopo full ou cashin. |
| valornumberobrigatório | Valor em reais (150.50 = R$ 150,50). De 1.00 a 100000.00, dentro do limite de cobrança da sua conta. |
| nome_pagadorstringopcional | Nome do pagador (até 255). |
| documento_pagadorstringopcional | CPF (11 dígitos) ou CNPJ (14), com ou sem pontuação. Sem ele (ou só zeros), a API usa o CPF/CNPJ da sua conta. |
| email_pagadorstringopcional | E-mail do pagador (até 255). |
| celularstringopcional | Telefone do pagador (até 20). |
| descricaostringopcional | Descrição do pagamento (até 255). |
| webhookstring (url)opcional | URL pública que recebe os webhooks desta cobrança (http ou https; porta 80 a 443 ou de 3000 em diante). |
| magic_idstringopcional | Seu identificador (até 255). Volta no webhook de pago em data.magic_id. |
| split_usersarrayopcional | Divide o valor com outras contas — veja Split de pagamento. |
Resposta 200 OK
| Campo | Descrição |
|---|---|
| statusstring | "success" |
| messagestring | "ok" |
| paymentCodestring | PIX copia-e-cola (BR Code). Gere o QR Code a partir dele. |
| idTransactionstring | ID da cobrança. Guarde: casa com o data.tx_id do webhook de pago e serve para a consulta. |
| paymentCodeBase64string | Imagem PNG do QR em base64 ou o próprio copia-e-cola em base64, conforme a rede que processou a cobrança. |
Erros
| Status | Resposta | Quando ocorre |
|---|---|---|
| 403 | {"status": "error", "message": "Geracao de PIX desabilitada para esta conta."} | Geração desabilitada na conta. |
| 422 | {"error": "Valor minimo por transacao e R$ 1,00"} | valor abaixo de 1.00 ou inválido. |
| 422 | {"error": "Valor maximo por transacao excedido"} | Acima de R$ 100.000,00. |
| 422 | {"status": "error", "message": "Valor excede o limite de cashin (R$ 500.00)."} | Acima do limite de cobrança da sua conta. |
| 422 | {"error": "Documento do pagador deve conter 11 ou 14 digitos"} | Documento com tamanho errado. |
| 422 | {"error": "Documento do pagador obrigatorio"} | Sem documento e sem CPF/CNPJ na sua conta. |
| 422 | {"error": "URL de webhook invalida ou nao permitida"} | Protocolo, porta ou host não permitidos (localhost, IP privado). |
| 422 | {"error": "Split excede o teto de 90% do valor (maximo R$ 135.45)"} | Split acima do teto. |
| 422 | {"error": "Dados invalidos", "detalhes": [...]} | Corpo fora do formato (ex.: item de split_users inválido). |
| 500 | {"error": "..."} | A rede de pagamento não gerou o QR. Tente de novo com uma nova X-Idempotency-Key. |
Autenticação, rate limit e idempotência: Erros.
curl -X POST https://api.nxgate.com.br/pix/gerar \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 6f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"valor": 150.50,
"nome_pagador": "João Silva",
"documento_pagador": "12345678901",
"webhook": "https://seusite.com.br/webhook/nxgate",
"magic_id": "pedido-123"
}'import { randomUUID } from "node:crypto";
const resposta = await fetch("https://api.nxgate.com.br/pix/gerar", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Idempotency-Key": randomUUID(), // reuse só para repetir esta mesma operação
},
body: JSON.stringify({
api_key: process.env.NXGATE_API_KEY,
valor: 150.50,
nome_pagador: "João Silva",
documento_pagador: "12345678901",
webhook: "https://seusite.com.br/webhook/nxgate",
magic_id: "pedido-123",
}),
});
const dados = await resposta.json();
if (dados.status !== "success") throw new Error(dados.error || dados.message);
console.log(dados.idTransaction); // guarde no pedido
console.log(dados.paymentCode); // copia-e-cola: mostre e gere o QR a partir dele<?php
$ch = curl_init('https://api.nxgate.com.br/pix/gerar');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Idempotency-Key: ' . bin2hex(random_bytes(16)), // reuse só para repetir esta operação
],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'),
'valor' => 150.50,
'nome_pagador' => 'João Silva',
'documento_pagador' => '12345678901',
'webhook' => 'https://seusite.com.br/webhook/nxgate',
'magic_id' => 'pedido-123',
]),
]);
$dados = json_decode((string) curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http !== 200 || ($dados['status'] ?? '') !== 'success') {
throw new RuntimeException($dados['error'] ?? $dados['message'] ?? "HTTP $http");
}
echo $dados['idTransaction']; // guarde no pedido
echo $dados['paymentCode']; // copia-e-cola: mostre e gere o QR a partir deleimport os
import uuid
import requests
resposta = requests.post(
"https://api.nxgate.com.br/pix/gerar",
headers={"X-Idempotency-Key": str(uuid.uuid4())}, # reuse só para repetir esta operação
json={
"api_key": os.environ["NXGATE_API_KEY"],
"valor": 150.50,
"nome_pagador": "João Silva",
"documento_pagador": "12345678901",
"webhook": "https://seusite.com.br/webhook/nxgate",
"magic_id": "pedido-123",
},
timeout=30,
)
dados = resposta.json()
if resposta.status_code != 200 or dados.get("status") != "success":
raise RuntimeError(dados.get("error") or dados.get("message"))
print(dados["idTransaction"]) # guarde no pedido
print(dados["paymentCode"]) # copia-e-cola: mostre e gere o QR a partir dele{
"status": "success",
"message": "ok",
"paymentCode": "00020101021226850014br.gov.bcb.pix...6304A1B2",
"idTransaction": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"paymentCodeBase64": "iVBORw0KGgoAAAANSUhEUgAAAQ4AAAEOCAYAAAB4sfmn..."
}Consultar status de um PIX
Consulta uma cobrança PIX (BRL) criada pela /pix/gerar ou pela /v2/cobranca. Use para confirmar o pagamento depois do webhook e para reconciliar. A /v2/consultar faz o mesmo, devolve mais detalhes e também acha cobranças em outras moedas (esta rota responde 404 para elas).
Até 10.000 consultas/min por IP. Depois de 50 falhas de autenticação do mesmo IP em 5 minutos, 429 até a janela acabar (detalhes).
transaction_id aceita o idTransaction devolvido na geração ou o transaction_id do webhook PIX Pendente — os dois identificam a cobrança.
Parâmetros do corpo
| Campo | Descrição |
|---|---|
| api_keystringobrigatório | Chave com escopo full ou cashin. A cobrança precisa ser da conta dessa chave. |
| transaction_idstringobrigatório | Identificador da cobrança (veja acima). |
Resposta 200 OK
| Campo | Descrição |
|---|---|
| statusstring | "paid" (pago) ou "waiting" (ainda não confirmado). |
| transaction_idstring | Eco do identificador consultado. |
Só paid confirma o pagamento. Pagamento em análise (acima do limite de liberação automática da conta) responde waiting até ser liberado, mesmo depois do webhook de pago.
Erros
| Status | Resposta | Quando ocorre |
|---|---|---|
| 400 | {"error": "api_key e transaction_id sao obrigatorios"} | Faltou um dos campos. |
| 401 | {"error": "API Key invalida"} | Chave inexistente (ou API Key revogada ou inativa). |
| 403 | {"error": "esta API Key nao tem permissao de geracao de PIX"} | Chave sem escopo cashin. |
| 403 | {"error": "Usuario nao vinculado"} | Chave sem conta vinculada. |
| 404 | {"error": "Transacao nao encontrada"} | Não existe, é de outra conta ou é de outra moeda. |
| 429 | {"error": "Muitas tentativas. Aguarde."} | Muitas falhas de autenticação do mesmo IP. |
curl -X POST https://api.nxgate.com.br/pix/consultar \
-H "Content-Type: application/json" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90"
}'const resposta = await fetch("https://api.nxgate.com.br/pix/consultar", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
api_key: process.env.NXGATE_API_KEY,
transaction_id: "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
}),
});
const dados = await resposta.json();
if (!resposta.ok) throw new Error(dados.error || dados.message);
if (dados.status === "paid") {
// pagamento confirmado: libere o pedido (uma vez só)
}<?php
$ch = curl_init('https://api.nxgate.com.br/pix/consultar');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'),
'transaction_id' => 'e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90',
]),
]);
$dados = json_decode((string) curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http !== 200) {
throw new RuntimeException($dados['error'] ?? "HTTP $http");
}
if ($dados['status'] === 'paid') {
// pagamento confirmado: libere o pedido (uma vez só)
}import os
import requests
resposta = requests.post(
"https://api.nxgate.com.br/pix/consultar",
json={
"api_key": os.environ["NXGATE_API_KEY"],
"transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
},
timeout=30,
)
dados = resposta.json()
if resposta.status_code != 200:
raise RuntimeError(dados.get("error"))
if dados["status"] == "paid":
pass # pagamento confirmado: libere o pedido (uma vez só){
"status": "paid",
"transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90"
}{
"error": "Transacao nao encontrada"
}Solicitar saque PIX
Envia um PIX do seu saldo NxGate para uma chave PIX. O valor e a taxa saem do saldo na hora; o resultado final chega pelos webhooks de saque.
Rate limit: 100/min por API key e 60/min por IP — o contador por IP é compartilhado com as outras rotas (detalhes). Idempotência: X-Idempotency-Key, prazo de 10 minutos.
IP liberado é obrigatório. O saque só é aceito vindo de um IP cadastrado em Painel → API → IP Seguro para Saques (até 20 IPs; pede o PIN). Sem isso: 403 {"status": "error", "message": "IP nao autorizado."}. O IP precisa bater exatamente — se o seu servidor sair por IPv6, cadastre o IPv6 ou force IPv4 (por exemplo, curl -4). Contas de funcionário usam também os IPs da conta principal e precisam da permissão de saque.
- Chave com escopo
fulloucashout. - Conta aprovada e com saque habilitado.
- Saldo para o valor + a taxa do saque.
Parâmetros do corpo
| Campo | Descrição |
|---|---|
| api_keystringobrigatório | Chave com escopo full ou cashout. |
| valornumberobrigatório | Valor em reais a enviar. Mínimo R$ 1,00; máximo R$ 50.000,00 por saque (ou o limite de saque da conta). |
| chave_pixstringobrigatório | Chave PIX de destino (até 255 caracteres). |
| tipo_chaveenumobrigatório | CPF, CNPJ, PHONE, EMAIL ou RANDOM (chave aleatória). |
| documentostringopcional | CPF/CNPJ do recebedor (até 20 caracteres), para o comprovante. Com tipo_chave CPF ou CNPJ, a própria chave já é usada. |
| webhookstring (url)opcional | URL pública que recebe os webhooks deste saque (até 255). |
| magic_idstringopcional | Aceito (até 255), mas hoje não volta nos webhooks de saque — case pelo internalreference. |
Resposta 200 OK
| Campo | Descrição |
|---|---|
| statusstring | "success" — saque aceito. "error" também pode vir com HTTP 200 (veja Erros). |
| messagestring | "Saque enviado para processamento automatico." ou, em contas com aprovação manual, "Saque registrado e aguardando aprovacao manual.". |
| internalreferenceuuid | ID do saque. Guarde: volta como idTransaction nos webhooks de saque. |
| valores.solicitadonumber | Valor pedido. |
| valores.taxanumber | Taxa do saque. |
| valores.valor_liquidonumber | Valor que o recebedor recebe. |
| valores.total_debitadonumber | Total que saiu do seu saldo. |
Quando o saldo não cobre a taxa: se o saldo cobre o valor mas não o valor + taxa, a taxa é descontada do valor enviado — total_debitado = valor e valor_liquido = valor − taxa (o líquido precisa ficar em R$ 1,00 ou mais). Confira sempre os valores da resposta.
Erros
| Status | Resposta | Quando ocorre |
|---|---|---|
| 200 | {"status": "error", "message": "Saldo insuficiente para cobrir o valor do saque + taxas.", "detalhes": {"solicitado": 1000, "taxa": 15.5, "total_necessario": 1015.5, "disponivel": 500}} | Saldo não cobre o saque. Nada é debitado. |
| 200 | {"status": "error", "message": "Campos invalidos ou ausentes.", "erros": {"chave_pix": ["Required"], "tipo_chave": ["Required"]}} | Corpo fora do formato. |
| 403 | {"status": "error", "message": "IP nao autorizado."} | IP fora da lista do IP Seguro para Saques. |
| 403 | {"error": "esta API Key nao tem permissao de saque"} | Chave sem escopo cashout. |
| 403 | {"status": "error", "message": "Saque PIX desabilitado para esta conta."} | Saque desabilitado na conta. |
| 403 | {"status": "error", "message": "Conta nao liberada para saques."} | Conta ainda não aprovada. |
| 403 | {"status": "error", "message": "Funcionario sem permissao de saque."} | Conta de funcionário sem a permissão. |
| 422 | {"status": "error", "message": "Valor minimo de saque e R$ 1,00"} | Abaixo de R$ 1,00. |
| 422 | {"status": "error", "message": "Valor maximo por saque excedido"} | Acima de R$ 50.000,00. |
| 422 | {"status": "error", "message": "Valor excede o limite de saque (R$ 2000.00)."} | Acima do limite de saque da conta. |
| 422 | {"status": "error", "message": "Valor liquido apos taxa seria menor que R$ 1,00."} | Com a taxa descontada, sobraria menos de R$ 1,00. |
| 422 | {"error": "URL de webhook invalida ou nao permitida"} | Webhook inválido. |
| 503 | {"status": "error", "message": "Saques temporariamente indisponiveis. Tente novamente mais tarde."} | Saques pausados no momento. Tente mais tarde. |
409 (idempotência), 429 e 500: veja Erros. Não existe rota para consultar um saque: acompanhe pelos webhooks de saque e pelo painel.
# rode do servidor cujo IP está liberado (IP Seguro para Saques)
curl -X POST https://api.nxgate.com.br/pix/sacar \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 7a1e3c5b-9d2f-4b6a-8c0e-2f4a6b8c0d1e" \
-d '{
"api_key": "nx_live_sua_chave_aqui",
"valor": 500.00,
"chave_pix": "[email protected]",
"tipo_chave": "EMAIL",
"webhook": "https://seusite.com.br/webhook/nxgate"
}'import { randomUUID } from "node:crypto";
const resposta = await fetch("https://api.nxgate.com.br/pix/sacar", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Idempotency-Key": randomUUID(), // reuse só para repetir esta mesma operação
},
body: JSON.stringify({
api_key: process.env.NXGATE_API_KEY,
valor: 500.00,
chave_pix: "[email protected]",
tipo_chave: "EMAIL",
webhook: "https://seusite.com.br/webhook/nxgate",
}),
});
const dados = await resposta.json();
// HTTP 200 nem sempre é sucesso: confira o status
if (dados.status !== "success") throw new Error(dados.message || dados.error);
console.log(dados.internalreference); // guarde: volta como idTransaction nos webhooks
console.log(dados.valores); // solicitado, taxa, valor_liquido, total_debitado<?php
$ch = curl_init('https://api.nxgate.com.br/pix/sacar');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Idempotency-Key: ' . bin2hex(random_bytes(16)), // reuse só para repetir esta operação
],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'),
'valor' => 500.00,
'chave_pix' => '[email protected]',
'tipo_chave' => 'EMAIL',
'webhook' => 'https://seusite.com.br/webhook/nxgate',
]),
]);
$dados = json_decode((string) curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// HTTP 200 nem sempre é sucesso: confira o status
if (($dados['status'] ?? '') !== 'success') {
throw new RuntimeException($dados['message'] ?? $dados['error'] ?? "HTTP $http");
}
echo $dados['internalreference']; // guarde: volta como idTransaction nos webhooksimport os
import uuid
import requests
resposta = requests.post(
"https://api.nxgate.com.br/pix/sacar",
headers={"X-Idempotency-Key": str(uuid.uuid4())}, # reuse só para repetir esta operação
json={
"api_key": os.environ["NXGATE_API_KEY"],
"valor": 500.00,
"chave_pix": "[email protected]",
"tipo_chave": "EMAIL",
"webhook": "https://seusite.com.br/webhook/nxgate",
},
timeout=30,
)
dados = resposta.json()
# HTTP 200 nem sempre é sucesso: confira o status
if dados.get("status") != "success":
raise RuntimeError(dados.get("message") or dados.get("error"))
print(dados["internalreference"]) # guarde: volta como idTransaction nos webhooks
print(dados["valores"]){
"status": "success",
"message": "Saque enviado para processamento automatico.",
"internalreference": "7d9f2c1a-4b3e-4f6a-9c8d-1e2f3a4b5c6d",
"valores": {
"solicitado": 500,
"taxa": 7.5,
"valor_liquido": 500,
"total_debitado": 507.5
}
}{
"status": "success",
"message": "Saque enviado para processamento automatico.",
"internalreference": "7d9f2c1a-4b3e-4f6a-9c8d-1e2f3a4b5c6d",
"valores": {
"solicitado": 500,
"taxa": 7.5,
"valor_liquido": 492.5,
"total_debitado": 500
}
}{
"status": "error",
"message": "Saldo insuficiente para cobrir o valor do saque + taxas.",
"detalhes": {
"solicitado": 1000,
"taxa": 15.5,
"total_necessario": 1015.5,
"disponivel": 500
}
}Consultar saldo da conta
Saldo em tempo real (sem cache): o que está disponível para saque, os valores retidos e os acumulados de vendas e saques.
Rate limit: 60/min por API key e 120/min por IP. Para acompanhar pagamentos, use webhooks — não faça polling de saldo.
Mesma regra do saque: o IP de origem precisa estar em IP Seguro para Saques — sem isso, a API responde 403 com IP nao autorizado. — e a chave precisa do escopo full ou saldo.
Parâmetros do corpo
| Campo | Descrição |
|---|---|
| api_keystringobrigatório | Sua chave. O saldo é o da conta dona dela. |
Resposta 200 OK
| Campo | Descrição |
|---|---|
| statusstring | "success" |
| data.account_idstring | Identificador da conta. |
| data.usernamestring | Usuário da conta. |
| data.moedastring | Sempre "BRL". |
| data.disponivelnumber | Saldo disponível para saque, em reais. |
| data.congeladonumber | Valor retido (análise ou segurança). Não pode ser sacado. |
| data.congelado_mednumber | Valor retido por MED (Mecanismo Especial de Devolução do PIX). |
| data.total_vendasnumber | Acumulado histórico de vendas da conta. |
| data.total_saquesnumber | Acumulado histórico de saques da conta. |
| data.consultado_emstring | Momento da consulta (ISO 8601, UTC). |
Valores em outras moedas aguardando conversão não entram aqui: aparecem no painel e passam a contar no disponível depois da conversão para real.
Erros
| Status | Resposta | Quando ocorre |
|---|---|---|
| 401 | {"error": "API Key invalida"} | Chave inválida (também api_key obrigatoria e API Key revogada ou inativa). |
| 403 | {"status": "error", "message": "IP nao autorizado."} | IP fora do IP Seguro para Saques. |
| 403 | {"error": "esta API Key nao tem permissao de consulta de saldo"} | Chave sem escopo saldo. |
| 409 | {"status": "error", "message": "Conta sem processador configurado."} | Fale com o suporte. |
| 429 | {"error": "Too many requests. Tente novamente em breve."} | Rate limit. |
# rode do servidor cujo IP está liberado (IP Seguro para Saques)
curl -X POST https://api.nxgate.com.br/saldo \
-H "Content-Type: application/json" \
-d '{
"api_key": "nx_live_sua_chave_aqui"
}'const resposta = await fetch("https://api.nxgate.com.br/saldo", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
api_key: process.env.NXGATE_API_KEY,
}),
});
const dados = await resposta.json();
if (dados.status !== "success") throw new Error(dados.message || dados.error);
console.log(dados.data.disponivel); // 1523.47<?php
$ch = curl_init('https://api.nxgate.com.br/saldo');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'),
]),
]);
$dados = json_decode((string) curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (($dados['status'] ?? '') !== 'success') {
throw new RuntimeException($dados['message'] ?? $dados['error'] ?? "HTTP $http");
}
echo $dados['data']['disponivel'];import os
import requests
resposta = requests.post(
"https://api.nxgate.com.br/saldo",
json={
"api_key": os.environ["NXGATE_API_KEY"],
},
timeout=30,
)
dados = resposta.json()
if dados.get("status") != "success":
raise RuntimeError(dados.get("message") or dados.get("error"))
print(dados["data"]["disponivel"]) # 1523.47{
"status": "success",
"data": {
"account_id": "1042",
"username": "minhaloja",
"moeda": "BRL",
"disponivel": 1523.47,
"congelado": 0,
"congelado_med": 0,
"total_vendas": 98420.1,
"total_saques": 96890,
"consultado_em": "2026-10-05T17:05:00.000Z"
}
}{
"status": "error",
"message": "IP nao autorizado."
}Webhooks — como funciona
Quando uma transação muda de status, a NxGate faz um HTTP POST com JSON na URL que você mandou no campo webhook ao criar a cobrança ou o saque. O formato é o mesmo, qualquer que seja a rede de pagamento que processou.
Como configurar
Mande webhook com uma URL pública (http ou https — prefira HTTPS) em /v2/cobranca, /pix/gerar ou /pix/sacar. Cada transação usa a URL que veio nela.
Requisitos do seu endpoint
- Aceitar POST com corpo JSON.
- Responder 2xx para confirmar. Qualquer outro status, erro de rede ou demora conta como falha.
- Timeout: 12 segundos (cobrança) e 10 segundos (saque).
- Não depender de IP de origem fixo: confira a assinatura.
Não há reenvio automático. Se a entrega falhar, ela não é repetida sozinha. Responda 2xx rápido (processe em fila), consulte a transação para reconciliar e, se precisar, reenvie pelo painel.
Histórico e reenvio
Em Painel → Webhooks você vê cada entrega dos últimos 30 dias — status HTTP, payload enviado, resposta do seu servidor e duração — e pode reenviar. O reenvio manda o mesmo payload, com assinatura nova.
# Cobrança (PIX e outras moedas)
PIX Pendente → status: "pending" (sem type)
PIX Pago → QR_CODE_COPY_AND_PASTE_PAID
# Saque
Saque Pago → PIX_CASHOUT_PAID
Saque Falhou → PIX_CASHOUT_ERROR
Saque Estornado → PIX_CASHOUT_REFUNDHeaders enviados
| Header | Valor | Em quais |
|---|---|---|
User-Agent | NXGATE-Webhook/1.0 | todos |
Content-Type | application/json | todos |
X-NXGate-Timestamp | Momento do envio (Unix, em segundos) | todos |
X-NXGate-Signature | sha256=<hex> — veja Assinatura | todos |
Accept | application/json | cobrança (pendente e pago) e reenvios pelo painel |
X-Idempotency-Key | Identificador da entrega: muda a cada envio, inclusive no reenvio. Para deduplicar, use o ID da transação. | cobrança (pendente e pago) e reenvios pelo painel |
IP de origem
Todos os webhooks saem do IP fixo 56.125.57.117. Se o seu servidor ou firewall só aceita IPs conhecidos, libere esse IP para a URL do seu webhook.
Liberar o IP é opcional. O que prova que o webhook veio da NxGate é a assinatura; use a lista de IPs como camada extra, não no lugar dela.
Eventos
| Evento | Quando | Identificador |
|---|---|---|
PIX Pendente status: "pending" | Logo depois de criar a cobrança | transaction_id |
PIX Pago QR_CODE_COPY_AND_PASTE_PAID | Pagamento confirmado pela rede | data.tx_id |
Saque Pago PIX_CASHOUT_PAID | Saque concluído | idTransaction |
Saque Falhou PIX_CASHOUT_ERROR | Saque recusado ou com falha | idTransaction |
Saque Estornado PIX_CASHOUT_REFUND | PIX do saque devolvido | idTransaction |
Não existe webhook de MED nem de estorno de cobrança: valores retidos por MED aparecem em congelado_med no POST /saldo e no painel.
Assinatura dos webhooks (HMAC)
Os webhooks vão assinados. Conferindo a assinatura, você sabe que o webhook saiu da NxGate e não foi alterado no caminho.
Plataforma multi-cliente não usa HMAC. Se cada cliente da sua plataforma cadastra a própria api_key, não valide a assinatura: confirme o pagamento só pela POST /v2/consultar, com a api_key do cliente dono do pedido. A validação HMAC é para quem integra 1 conta só.
- Leia o corpo bruto da requisição — os bytes exatos, antes de converter o JSON.
- Monte a mensagem: o valor do header
X-NXGate-Timestamp+.+ o corpo bruto. - Calcule o HMAC-SHA256 dessa mensagem com o seu segredo e compare, em tempo constante, com o header
X-NXGate-Signature, que vem comosha256=<hex>. - Recuse se o timestamp estiver a mais de 5 minutos do seu relógio (proteção contra repetição).
Qual é o segredo
É o Segredo de assinatura de webhook (HMAC) que aparece em Painel → API. Se a tela não mostrar esse segredo, a assinatura usa como segredo a sua chave Interna (a API key marcada como Interna).
Confira sobre o corpo bruto. Se o seu framework já converteu o JSON e você serializar de novo, os bytes podem mudar e a assinatura não bate.
A assinatura prova a origem; a consulta prova o estado. Para liberar um pedido, faça as duas: assinatura válida e POST /v2/consultar respondendo paid (em plataforma multi-cliente, só a consulta).
import crypto from "node:crypto";
// corpo = Buffer com o corpo BRUTO (ex.: express.raw), antes de JSON.parse
export function assinaturaValida(corpo, timestamp, assinatura, segredo) {
if (!/^\d+$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // 5 min
const esperado = "sha256=" + crypto
.createHmac("sha256", segredo)
.update(timestamp + ".")
.update(corpo)
.digest("hex");
const a = Buffer.from(assinatura || "");
const b = Buffer.from(esperado);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// uso: assinaturaValida(req.body, req.get("X-NXGate-Timestamp") || "",
// req.get("X-NXGate-Signature"), process.env.NXGATE_WEBHOOK_SECRET)<?php
// $corpo = file_get_contents('php://input') — o corpo BRUTO, antes de json_decode
function assinaturaValida(string $corpo, string $timestamp, string $assinatura, string $segredo): bool
{
if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) { // 5 min
return false;
}
$esperado = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $corpo, $segredo);
return hash_equals($esperado, $assinatura);
}
// uso:
// assinaturaValida(file_get_contents('php://input'),
// $_SERVER['HTTP_X_NXGATE_TIMESTAMP'] ?? '', $_SERVER['HTTP_X_NXGATE_SIGNATURE'] ?? '',
// (string) getenv('NXGATE_WEBHOOK_SECRET'));import hashlib
import hmac
import time
def assinatura_valida(corpo: bytes, timestamp: str, assinatura: str, segredo: str) -> bool:
"""corpo = bytes BRUTOS da requisição, antes de json.loads."""
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300: # 5 min
return False
esperado = "sha256=" + hmac.new(
segredo.encode(), timestamp.encode() + b"." + corpo, hashlib.sha256
).hexdigest()
return hmac.compare_digest(esperado, assinatura)
# uso (FastAPI): assinatura_valida(await request.body(),
# request.headers.get("x-nxgate-timestamp", ""),
# request.headers.get("x-nxgate-signature", ""), os.environ["NXGATE_WEBHOOK_SECRET"])PIX Gerado (Pendente)
Enviado logo depois que a cobrança é criada, antes do pagamento. É informativo: responda 2xx e siga — nada depende dele.
| Campo | Descrição |
|---|---|
| statusstring | "pending" |
| transaction_idstring | ID interno da cobrança na NxGate. Em BRL é diferente do idTransaction devolvido na geração (as consultas aceitam os dois); em outras moedas é o próprio idTransaction. |
| amountnumber | Valor da cobrança. |
| messagestring | "PIX gerado, aguardando pagamento" — em outras moedas, "Cobranca gerada, aguardando pagamento". |
| adquirentestring | Nome interno da rede de pagamento que gerou a cobrança. Informativo: pode mudar a qualquer momento — não use em regras. |
| currencystring | Só em outras moedas. |
| payment_methodstring | Só em outras moedas. |
Este evento não tem type: identifique pelo status: "pending".
{
"status": "pending",
"transaction_id": "1b4e28ba-2fa1-4d2b-9c3e-6f1a2b3c4d5e",
"amount": 150.5,
"message": "PIX gerado, aguardando pagamento",
"adquirente": "nxgate"
}PIX Pago QR_CODE_COPY_AND_PASTE_PAID
Enviado quando a rede de pagamento confirma o PIX. O formato é o mesmo, qualquer que seja a rede; vários campos existem por compatibilidade e podem vir vazios. Timeout: 12 s.
| Campo | Descrição |
|---|---|
| typestring | "QR_CODE_COPY_AND_PASTE_PAID" |
| data.tx_idstring | O idTransaction da geração. Use este campo para casar com o pedido. |
| data.amountnumber | Valor pago (em reais; em outras moedas, na moeda da cobrança). |
| data.magic_idstring | null | O seu magic_id, se enviado na geração. |
| data.statusstring | "PAID"; data.worked vem true. |
| data.end_to_endstring | null | Identificador end-to-end do PIX (E2E). |
| data.payment_datestring | null | Data e hora do pagamento informadas pela rede. Pode vir null e o formato varia. |
| data.debtor_namestring | null | Nome do pagador, quando a rede informa. |
| data.debtor_documentstring | null | CPF/CNPJ do pagador (só dígitos); data.type_document diz se é "CPF" ou "CNPJ". |
| data.qr_code_idstring | ID da cobrança — em geral igual ao tx_id, mas em algumas redes é o ID da própria rede. Case pelo tx_id. |
| data.tagstring | Em geral igual ao tx_id; pode vir "NONE". |
| data.pix_copy_and_pastestring | null | O copia-e-cola da cobrança. |
| data.currency, data.payment_methodstring | Só em outras moedas (detalhes). |
Os demais campos — amount_chargeback, base_64_image, base_64_image_url, account_number, agency_number, debtor_institution, debtor_institution_number, due_date, expiration_date, fee, fine, fine_date e type_fine — existem por compatibilidade e quase sempre vêm 0, null ou "NONE". fee não é a taxa da NxGate.
Confirme antes de liberar: chame POST /v2/consultar com data.tx_id e libere só com paid. Se o pagamento passar do limite de liberação automática da conta, este webhook é enviado mesmo assim, mas o valor fica em análise e a consulta responde waiting até a liberação — e não chega outro webhook quando liberar.
{
"type": "QR_CODE_COPY_AND_PASTE_PAID",
"data": {
"amount": 150.5,
"amount_chargeback": 0,
"base_64_image": null,
"base_64_image_url": null,
"account_number": null,
"agency_number": null,
"debtor_document": "12345678901",
"debtor_institution": null,
"debtor_institution_number": null,
"debtor_name": "João Silva",
"due_date": null,
"end_to_end": "E00000000202610051530abcdefghijk",
"expiration_date": null,
"fee": 0,
"fine": null,
"fine_date": null,
"payment_date": "2026-10-05T15:30:00.000Z",
"pix_copy_and_paste": "00020101021226850014br.gov.bcb.pix...6304A1B2",
"qr_code_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"status": "PAID",
"tag": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"tx_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90",
"type_document": "CPF",
"type_fine": "NONE",
"worked": true,
"magic_id": "pedido-123"
}
}Saque Pago PIX_CASHOUT_PAID
Enviado quando o PIX do saque é concluído. Timeout: 10 s.
| Campo | Descrição |
|---|---|
| typestring | "PIX_CASHOUT_PAID" |
| idTransactionstring | O internalreference devolvido pelo /pix/sacar. Use para casar. |
| statusstring | "saque-pago"; worked vem true. |
| end_to_endstring | null | Identificador end-to-end do PIX enviado. |
| messagestring | "Saque realizado" |
| amountnumber | O valor_liquido do saque — o que o recebedor recebe (ou receberia). |
| keystring | Chave PIX de destino. |
| transaction_idstring | null | Referência do saque na rede de pagamento (pode vir null). |
| payment_datestring | Momento do envio deste webhook (ISO 8601, UTC). |
| feenumber | Sempre 0 (a taxa está em valores.taxa na resposta do saque). |
Um saque pago ainda pode ser devolvido depois: aí chega o PIX_CASHOUT_REFUND com o mesmo idTransaction.
{
"amount": 500,
"end_to_end": "E00000000202610051530abcdefghijk",
"fee": 0,
"key": "[email protected]",
"payment_date": "2026-10-05T15:31:02.418Z",
"status": "saque-pago",
"transaction_id": "4f8a2c1e9b7d",
"idTransaction": "7d9f2c1a-4b3e-4f6a-9c8d-1e2f3a4b5c6d",
"type": "PIX_CASHOUT_PAID",
"worked": true,
"message": "Saque realizado"
}Saque com Falha PIX_CASHOUT_ERROR
Enviado quando o saque é recusado ou falha. O valor debitado (valor + taxa) volta automaticamente para o seu saldo NxGate. Timeout: 10 s.
| Campo | Descrição |
|---|---|
| typestring | "PIX_CASHOUT_ERROR" |
| idTransactionstring | O internalreference do saque. |
| statusstring | "saque-falhou"; worked vem false. |
| errorstring | Motivo da falha, em texto livre (da rede de pagamento ou do sistema). Mostre, mas não use em regras. |
| end_to_endstring | null | Normalmente null. |
| amountnumber | O valor_liquido do saque — o que o recebedor recebe (ou receberia). |
| keystring | Chave PIX de destino. |
| transaction_idstring | null | Referência do saque na rede de pagamento (pode vir null). |
| payment_datestring | Momento do envio deste webhook (ISO 8601, UTC). |
| feenumber | Sempre 0 (a taxa está em valores.taxa na resposta do saque). |
No seu sistema, marque o saque como falho e, se você debitou o usuário por conta própria, devolva o saldo dele.
{
"amount": 500,
"end_to_end": null,
"fee": 0,
"key": "[email protected]",
"payment_date": "2026-10-05T15:31:02.418Z",
"status": "saque-falhou",
"transaction_id": null,
"idTransaction": "7d9f2c1a-4b3e-4f6a-9c8d-1e2f3a4b5c6d",
"type": "PIX_CASHOUT_ERROR",
"worked": false,
"error": "Chave PIX nao encontrada"
}Saque Estornado PIX_CASHOUT_REFUND
Enviado quando o PIX do saque é devolvido — inclusive depois de já ter chegado o PIX_CASHOUT_PAID. O valor debitado volta para o seu saldo NxGate. Timeout: 10 s.
| Campo | Descrição |
|---|---|
| typestring | "PIX_CASHOUT_REFUND" |
| idTransactionstring | O internalreference do saque. |
| statusstring | "saque-estornado"; worked vem false. |
| messagestring | Motivo da devolução (ex.: "Saque devolvido"). |
| end_to_endstring | null | Identificador end-to-end, quando houver. |
| amountnumber | O valor_liquido do saque — o que o recebedor recebe (ou receberia). |
| keystring | Chave PIX de destino. |
| transaction_idstring | null | Referência do saque na rede de pagamento (pode vir null). |
| payment_datestring | Momento do envio deste webhook (ISO 8601, UTC). |
| feenumber | Sempre 0 (a taxa está em valores.taxa na resposta do saque). |
{
"amount": 500,
"end_to_end": "E00000000202610051530abcdefghijk",
"fee": 0,
"key": "[email protected]",
"payment_date": "2026-10-05T15:31:02.418Z",
"status": "saque-estornado",
"transaction_id": "4f8a2c1e9b7d",
"idTransaction": "7d9f2c1a-4b3e-4f6a-9c8d-1e2f3a4b5c6d",
"type": "PIX_CASHOUT_REFUND",
"worked": false,
"message": "Saque devolvido"
}Identificando a transação
Use estes campos para casar a notificação com o pedido ou o saque no seu sistema:
| Evento | Campo no webhook | Corresponde a |
|---|---|---|
| PIX Pago | data.tx_id | idTransaction da /v2/cobranca ou da /pix/gerar |
| PIX Pago (seu ID) | data.magic_id | o magic_id enviado na criação |
| PIX Pendente | transaction_id | em BRL, o ID interno da NxGate (diferente do idTransaction, mas aceito nas consultas); em outras moedas, o idTransaction |
| Saques (pago, falhou, estornado) | idTransaction | internalreference do /pix/sacar |
| Saques (referência da rede) | transaction_id | referência na rede de pagamento — pode vir null |
O magic_id só volta no webhook de cobrança paga. Nos saques, case pelo internalreference.
Exemplo de recebimento
Um único endpoint pode tratar todos os eventos. O roteiro: confira a assinatura, responda 2xx rápido, confirme na consulta e atualize o pedido de forma idempotente.
- Confira a assinatura com o corpo bruto.
- Diferencie pelo
type— o pendente vem comstatus: "pending"e semtype— e ignore o que não conhece. - PIX pago: confirme com
POST /v2/consultar(data.tx_id) antes de liberar. - Saques: case pelo
idTransaction. - Idempotência: só atualize se o status mudou — o mesmo evento pode chegar de novo num reenvio.
Quer isso pronto no seu framework? Use os prompts prontos.
import express from "express";
import crypto from "node:crypto";
const app = express();
const API = "https://api.nxgate.com.br";
const API_KEY = process.env.NXGATE_API_KEY; // só no servidor
const SEGREDO = process.env.NXGATE_WEBHOOK_SECRET; // Painel → API → segredo de assinatura
function assinaturaValida(corpo, ts, assinatura) {
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const esperado = "sha256=" + crypto.createHmac("sha256", SEGREDO)
.update(ts + ".").update(corpo).digest("hex");
const a = Buffer.from(assinatura || ""), b = Buffer.from(esperado);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// express.raw: a assinatura é conferida sobre o corpo BRUTO
app.post("/webhook/nxgate", express.raw({ type: "*/*" }), async (req, res) => {
if (!assinaturaValida(req.body, req.get("X-NXGate-Timestamp") || "", req.get("X-NXGate-Signature"))) {
return res.sendStatus(401);
}
const evento = JSON.parse(req.body.toString("utf8"));
res.sendStatus(200); // responda rápido; o resto roda em seguida
try {
if (evento.type === "QR_CODE_COPY_AND_PASTE_PAID") {
// confirme na API antes de liberar
const r = await fetch(`${API}/v2/consultar`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_key: API_KEY, transaction_id: evento.data.tx_id }),
});
const consulta = await r.json();
if (consulta.status === "paid") await marcarPedidoPago(evento.data.tx_id);
} else if (["PIX_CASHOUT_PAID", "PIX_CASHOUT_ERROR", "PIX_CASHOUT_REFUND"].includes(evento.type)) {
await atualizarSaque(evento.idTransaction, evento.type);
}
// status "pending" (sem type) e eventos desconhecidos: só ignore
} catch (erro) {
console.error("webhook nxgate", erro); // a reconciliação cobre o que falhar aqui
}
});
async function marcarPedidoPago(txId) { /* só se o pedido ainda não estiver pago */ }
async function atualizarSaque(idTransaction, tipo) { /* idempotente */ }
app.listen(3000);<?php
// webhook.php — recebe os webhooks da NxGate
$corpo = file_get_contents('php://input'); // corpo BRUTO
$ts = $_SERVER['HTTP_X_NXGATE_TIMESTAMP'] ?? '';
$assinatura = $_SERVER['HTTP_X_NXGATE_SIGNATURE'] ?? '';
$esperado = 'sha256=' . hash_hmac('sha256', $ts . '.' . $corpo, (string) getenv('NXGATE_WEBHOOK_SECRET'));
if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300 || !hash_equals($esperado, $assinatura)) {
http_response_code(401);
exit;
}
$evento = json_decode($corpo, true);
$tipo = $evento['type'] ?? '';
if ($tipo === 'QR_CODE_COPY_AND_PASTE_PAID') {
// confirme na API antes de liberar
$txId = $evento['data']['tx_id'];
$ch = curl_init('https://api.nxgate.com.br/v2/consultar');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'api_key' => getenv('NXGATE_API_KEY'),
'transaction_id' => $txId,
]),
]);
$consulta = json_decode((string) curl_exec($ch), true);
curl_close($ch);
if (($consulta['status'] ?? '') === 'paid') {
marcarPedidoPago($txId);
}
} elseif (in_array($tipo, ['PIX_CASHOUT_PAID', 'PIX_CASHOUT_ERROR', 'PIX_CASHOUT_REFUND'], true)) {
atualizarSaque($evento['idTransaction'], $tipo);
}
// status "pending" (sem type) e eventos desconhecidos: só ignore
http_response_code(200);
function marcarPedidoPago(string $txId): void { /* só se o pedido ainda não estiver pago */ }
function atualizarSaque(string $idTransaction, string $tipo): void { /* idempotente */ }import hashlib
import hmac
import json
import os
import time
import httpx
from fastapi import FastAPI, Request, Response
app = FastAPI()
API = "https://api.nxgate.com.br"
API_KEY = os.environ["NXGATE_API_KEY"] # só no servidor
SEGREDO = os.environ["NXGATE_WEBHOOK_SECRET"].encode() # Painel → API
def assinatura_valida(corpo: bytes, ts: str, assinatura: str) -> bool:
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
return False
esperado = "sha256=" + hmac.new(SEGREDO, ts.encode() + b"." + corpo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, assinatura)
def marcar_pedido_pago(tx_id: str) -> None:
... # só se o pedido ainda não estiver pago
def atualizar_saque(id_transaction: str, tipo: str) -> None:
... # idempotente
@app.post("/webhook/nxgate")
async def webhook(request: Request):
corpo = await request.body() # corpo BRUTO
if not assinatura_valida(corpo, request.headers.get("x-nxgate-timestamp", ""),
request.headers.get("x-nxgate-signature", "")):
return Response(status_code=401)
evento = json.loads(corpo)
tipo = evento.get("type")
if tipo == "QR_CODE_COPY_AND_PASTE_PAID":
tx_id = evento["data"]["tx_id"]
async with httpx.AsyncClient(timeout=10) as http: # confirme na API antes de liberar
r = await http.post(f"{API}/v2/consultar",
json={"api_key": API_KEY, "transaction_id": tx_id})
if r.json().get("status") == "paid":
marcar_pedido_pago(tx_id)
elif tipo in ("PIX_CASHOUT_PAID", "PIX_CASHOUT_ERROR", "PIX_CASHOUT_REFUND"):
atualizar_saque(evento["idTransaction"], tipo)
# status "pending" (sem type) e eventos desconhecidos: só ignore
return Response(status_code=200)Contexto da API para IA
Tudo o que uma IA precisa para escrever a sua integração, num bloco só: rotas, campos, respostas, webhooks, erros e regras. Copie e cole no Claude, ChatGPT, Cursor ou Copilot antes de pedir o código.
- Claude ou ChatGPT: cole o contexto e, na mesma mensagem, diga o que precisa — ou use um dos prompts prontos, que já levam este contexto junto.
- Cursor ou Copilot: baixe como
NXGATE_API.md, salve na raiz do projeto e cite o arquivo no chat (@NXGATE_API.mdno Cursor,#fileno Copilot). - O bloco mostra as moedas liberadas agora, lidas desta página. Quando uma moeda nova for liberada, copie de novo.
# Contexto da API NxGate (para IA)
Fonte: documentação oficial da NxGate (nxgate.com.br/doc). Use este texto como a especificação da integração; não invente campos, rotas ou eventos que não estão aqui.
## Básico
- Base URL: https://api.nxgate.com.br (só HTTPS). Para testar sem dinheiro de verdade existe o sandbox: https://sandbox.nxgate.com.br com a chave de teste nxg_test_… (veja "Sandbox" abaixo).
- Todas as rotas são POST, com corpo JSON e header Content-Type: application/json.
- Autenticação: campo "api_key" no corpo JSON (não existe header de autenticação).
- Escopos de chave: full (tudo), cashin (cobrança e consulta), cashout (saque), saldo (saldo).
- A api_key é segredo de servidor: variável de ambiente, nunca no front-end, no app ou no repositório. Chamadas de navegador são bloqueadas (CORS).
- Valores em dinheiro são decimais na moeda (150.50 = R$ 150,50). Exceção: split fixo, em centavos.
- Moedas liberadas agora: BRL.
## Sandbox (testes, sem dinheiro de verdade)
- Base URL: https://sandbox.nxgate.com.br, com a chave de teste nxg_test_… (gerada no painel em API → Sandbox). A chave de teste recebe 401 na produção, e a chave de produção recebe 401 no sandbox.
- Mesmas rotas, respostas, erros e webhooks da produção (mesmo payload, headers e assinatura). No sandbox o segredo HMAC do webhook é a própria chave de teste.
- A instrução de pagamento é fictícia e nada acontece sozinho: simule com POST /v2/sandbox/simular {"api_key", "transaction_id", "resultado"} (transaction_id = idTransaction da cobrança ou internalreference do saque).
- Cobrança: "pago" (webhook QR_CODE_COPY_AND_PASTE_PAID), "expirado", "estornado", "disputa" (esses três sem webhook, como na produção).
- Saque: "pago" (PIX_CASHOUT_PAID), "erro" (PIX_CASHOUT_ERROR; "motivo" opcional: generico, chave_invalida, documento, recusado, indisponivel), "estornado" (PIX_CASHOUT_REFUND).
- Resposta: 200 com "webhook": "disparado" | "sem_url" | "nao_se_aplica"; 404 transação de outra chave; 409 mudança que não vale; 422 resultado inválido.
- /saldo e /pix/sacar não exigem IP liberado no sandbox (na produção exigem).
- Limites baixos: 30 req/min por IP; por chave, 20/min no total, 5 cobranças/min, 5 simulações/min, 2 saques/min, 10 consultas+saldo/min e 200 operações/dia. Acima disso: 429 com Retry-After.
- Para ir à produção, troque só a URL base e a chave (deixe as duas em variáveis de ambiente) e remova as chamadas a /v2/sandbox/simular.
## Idempotência
- Header X-Idempotency-Key com um UUID v4 novo por operação em POST /v2/cobranca e /pix/gerar (vale 5 min) e /pix/sacar (vale 10 min). Guarde a chave junto do pedido/saque.
- Mesma chave depois de uma resposta HTTP 200: a API devolve a mesma resposta (nada novo é criado).
- Mesma chave enquanto a primeira ainda processa: 409 {"status": "error", "message": "Requisicao com esta X-Idempotency-Key ainda esta sendo processada. Aguarde a resposta da requisicao original."}.
- Se a primeira falhou: em /v2/cobranca e /pix/gerar use uma chave nova (a antiga fica presa por até 60 s); em /pix/sacar a chave é liberada, exceto "Saldo insuficiente", que fica guardado por 10 min.
- A chave vale para a API inteira: nunca reuse a mesma chave em operações diferentes.
## POST /v2/cobranca — criar cobrança (escopo cashin)
Corpo:
- api_key (string, obrigatório)
- valor (number, obrigatório): decimal na moeda (COP 10000 = dez mil pesos). Mínimo e máximo por cobrança: BRL 1.00 a 100000.00 (e o limite de cobrança da conta); MXN 20.00 a 5000.00; COP 10000 a 500000; ARS 2000 a 1000000; BOB 10 a 10000. Fora disso: 422.
- moeda (string, opcional): "BRL" (padrão), "MXN", "ARS", "COP" ou "BOB" — use só as liberadas.
- metodo (string, opcional): BRL "PIX"; MXN "SPEI" (padrão); ARS "BANK_TRANSFER"; COP "BREB" (padrão; NEQUI em breve); BOB "QR".
- nome_pagador, email_pagador, descricao (string, opcionais, até 255 caracteres); celular (string, opcional, até 20).
- documento_pagador (string, opcional): BRL = CPF (11) ou CNPJ (14), com ou sem pontuação; sem ele a API usa o CPF/CNPJ da conta. COP e BOB = documento do pagador no país (obrigatório). MXN e ARS = não usado.
- COP e BOB exigem celular, documento_pagador e email_pagador (sem eles: 422).
- webhook (string, opcional): URL pública http(s) — prefira HTTPS; porta 80 a 443 ou de 3000 em diante; localhost e IP privado são recusados.
- magic_id (string, opcional, até 255): seu ID (ex.: do pedido); volta no webhook de pago em data.magic_id.
- split_users (array, opcional): [{"username": "revendedor1", "type": "percentage", "value": 20}, {"username": "parceiro_fixo", "type": "fixed", "value": 500}] — "percentage" = % do valor; "fixed" = centavos inteiros. Total de até 90% do valor (50% abaixo de 2 unidades). A parte de um username inexistente é descontada do vendedor e não vai para ninguém (em BRL).
Resposta 200 (BRL):
```json
{"status": "success", "message": "ok", "paymentCode": "00020101021226850014br.gov.bcb.pix...6304A1B2", "idTransaction": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90", "paymentCodeBase64": "iVBORw0KGgoAAAANSUhEUgAAAQ4AAAEOCAYAAAB4sfmn...", "currency": "BRL", "paymentMethod": "PIX", "paymentInstruction": {"type": "pix", "pixCopyAndPaste": "00020101021226850014br.gov.bcb.pix...6304A1B2"}}
```
- Guarde idTransaction (texto opaco; o formato varia).
- Mostre paymentCode (copia-e-cola, com botão de copiar) e gere o QR Code a partir dele. Em COP/BREB, mostre também um botão para BAIXAR a imagem do QR: quem compra e paga no mesmo celular salva o QR e lê pela galeria no app do banco. Em BRL, paymentCodeBase64 pode ser a imagem PNG em base64 OU o próprio copia-e-cola em base64, conforme a rede — não dependa dele. Em outras moedas é null.
- paymentInstruction.type: "pix" (pixCopyAndPaste) · "clabe" (clabe; MXN/SPEI) · "cvu" (cvu, beneficiary, bankName; ARS) · "redirect" (url; COP/NEQUI, em breve) · "qr" (qr; BOB e COP/BREB) · "pendente" (instrução ainda sendo gerada: paymentCode null — consulte /v2/consultar em alguns segundos). Tipo desconhecido: mostre o paymentCode.
- Em BRL, /v2/cobranca é a /pix/gerar (mesma taxa e regras) + currency, paymentMethod e paymentInstruction.
Resposta 200 (MXN):
```json
{"status": "success", "message": "ok", "paymentCode": "646180157000000004", "idTransaction": "8f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5", "paymentCodeBase64": null, "currency": "MXN", "paymentMethod": "SPEI", "paymentInstruction": {"type": "clabe", "clabe": "646180157000000004"}}
```
## POST /v2/consultar — consultar cobrança (escopo cashin)
Corpo: {"api_key": "nx_live_sua_chave_aqui", "transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90"}
Resposta 200:
```json
{"status": "paid", "transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90", "currency": "BRL", "amount": 150.5, "payment_method": "PIX", "paid_at": "2026-10-05T12:30:00.000Z", "payment_instruction": null}
```
- status: "paid" ou "waiting". Só "paid" confirma. "waiting" = ainda não confirmado (aguardando pagamento ou em análise).
- Enquanto "waiting", payment_instruction traz a instrução (em BRL: {"type": "pix", "pix_copy_and_paste": "..."}); depois de pago, null.
- paid_at: ISO 8601 em UTC (com "Z"), em qualquer moeda; null enquanto não pago.
- Outras moedas: amount_brl (quanto a venda virou em reais no saldo) e converted_at (ISO UTC) depois da conversão para reais; null antes disso e sempre em BRL.
- transaction_id aceita o idTransaction e, em BRL, também o transaction_id do webhook PIX Pendente.
- Erros: 400 {"error": "api_key e transaction_id sao obrigatorios"} · 401 {"error": "API Key invalida"} · 403 {"error": "esta API Key nao tem permissao de geracao de PIX"} · 404 {"error": "Transacao nao encontrada"} · 429 {"error": "Muitas tentativas. Aguarde."}.
## Rotas v1 (continuam valendo, só BRL)
- POST /pix/gerar: mesmo corpo da /v2/cobranca sem moeda e metodo. Resposta: {"status": "success", "message": "ok", "paymentCode": "00020101021226850014br.gov.bcb.pix...6304A1B2", "idTransaction": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90", "paymentCodeBase64": "iVBORw0KGgoAAAANSUhEUgAAAQ4AAAEOCAYAAAB4sfmn..."}
- POST /pix/consultar: mesmo corpo da /v2/consultar. Resposta: {"status": "paid", "transaction_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90"} — não acha cobranças em outras moedas.
## Webhooks (a NxGate faz POST na URL do campo "webhook")
- Headers em todos: User-Agent: NXGATE-Webhook/1.0, Content-Type: application/json, X-NXGate-Timestamp (Unix, segundos), X-NXGate-Signature ("sha256=<hex>"). Os de cobrança também trazem Accept e X-Idempotency-Key (identificador da entrega; muda a cada envio).
- IP de origem de todos os webhooks: 56.125.57.117 (fixo; liberar no firewall é opcional, a assinatura é a garantia).
- Multi-cliente x 1 conta: antes de escrever o código, pergunte ao usuário se a plataforma é multi-cliente (cada cliente cadastra a própria api_key) ou usa 1 conta só (uma api_key). Multi-cliente NÃO valida HMAC: confirma o pagamento só pela consulta (/v2/consultar com a api_key do cliente dono do pedido). A assinatura abaixo é só para integrações de 1 conta.
- Assinatura (só 1 conta): hex = HMAC-SHA256(segredo, X-NXGate-Timestamp + "." + corpo bruto). Segredo = "Segredo de assinatura de webhook (HMAC)" em Painel → API (se não aparecer, é a API key Interna da conta). Compare em tempo constante e recuse timestamp com mais de 300 s de diferença.
- Responda 2xx em até 12 s (cobrança) ou 10 s (saque). Não há reenvio automático; há reenvio manual em Painel → Webhooks. O mesmo evento pode chegar de novo: processe de forma idempotente.
- Não existe webhook de MED/estorno de cobrança.
PIX Pendente (logo após criar a cobrança; não tem "type"):
```json
{"status": "pending", "transaction_id": "1b4e28ba-2fa1-4d2b-9c3e-6f1a2b3c4d5e", "amount": 150.5, "message": "PIX gerado, aguardando pagamento", "adquirente": "nxgate"}
```
- Em BRL, transaction_id é o ID interno da NxGate (diferente do idTransaction; as consultas aceitam os dois). "adquirente" é informativo (pode mudar; não use em regras).
- Em outras moedas: transaction_id = idTransaction, message "Cobranca gerada, aguardando pagamento" e mais "currency" e "payment_method".
PIX Pago:
```json
{"type": "QR_CODE_COPY_AND_PASTE_PAID", "data": {"amount": 150.5, "amount_chargeback": 0, "base_64_image": null, "base_64_image_url": null, "account_number": null, "agency_number": null, "debtor_document": "12345678901", "debtor_institution": null, "debtor_institution_number": null, "debtor_name": "João Silva", "due_date": null, "end_to_end": "E00000000202610051530abcdefghijk", "expiration_date": null, "fee": 0, "fine": null, "fine_date": null, "payment_date": "2026-10-05T15:30:00.000Z", "pix_copy_and_paste": "00020101021226850014br.gov.bcb.pix...6304A1B2", "qr_code_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90", "status": "PAID", "tag": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90", "tx_id": "e3f1c6a2-58b4-4d0e-9a7c-2b6d4f8e1a90", "type_document": "CPF", "type_fine": "NONE", "worked": true, "magic_id": "pedido-123"}}
```
- Case por data.tx_id (= idTransaction). data.magic_id = o seu magic_id.
- Antes de liberar o pedido, confirme com POST /v2/consultar (transaction_id = data.tx_id) e libere só com "paid". Se a consulta ainda disser "waiting", o pagamento está em análise: não peça para pagar de novo; consulte mais tarde.
- Outras moedas: mesmo formato + data.currency e data.payment_method; campos que só existem no PIX vêm null. Moeda = data.currency ?? "BRL".
- Contas gateway (liberação pelo suporte) recebem depois, na mesma URL, 1 webhook por venda em moeda estrangeira: {"type": "CURRENCY_CONVERTED", "data": {"status": "CONVERTED", "tx_id", "tag", "qr_code_id", "magic_id", "currency", "payment_method", "amount", "net_amount", "amount_brl", "exchange_rate", "converted_at", "conversion_id"}}. NÃO é novo pagamento: não libere o pedido de novo; use amount_brl (reais que entraram no saldo) para creditar o seller. Quem não usa, responde 200 e ignora.
Saques (case por idTransaction = internalreference do /pix/sacar):
```json
{"amount": 500, "end_to_end": "E00000000202610051530abcdefghijk", "fee": 0, "key": "[email protected]", "payment_date": "2026-10-05T15:31:02.418Z", "status": "saque-pago", "transaction_id": "4f8a2c1e9b7d", "idTransaction": "7d9f2c1a-4b3e-4f6a-9c8d-1e2f3a4b5c6d", "type": "PIX_CASHOUT_PAID", "worked": true, "message": "Saque realizado"}
```
- PIX_CASHOUT_PAID: status "saque-pago", worked true, message "Saque realizado". amount = valor enviado ao recebedor.
- PIX_CASHOUT_ERROR: status "saque-falhou", worked false, campo "error" com o motivo.
- PIX_CASHOUT_REFUND: status "saque-estornado", worked false, campo "message" com o motivo. Pode chegar depois do PAID.
- Em ERROR e REFUND, o valor debitado (valor + taxa) volta para o saldo NxGate.
- transaction_id é a referência da rede de pagamento (pode vir null).
## POST /pix/sacar — saque PIX (escopo cashout)
- Só é aceito vindo de um IP cadastrado em Painel → API → IP Seguro para Saques; senão 403 {"status": "error", "message": "IP nao autorizado."}. Se o servidor sair por IPv6, cadastre o IPv6 ou force IPv4.
- Corpo: api_key; valor (number, de R$ 1,00 a R$ 50.000,00 e dentro do limite de saque da conta); chave_pix (string); tipo_chave ("CPF", "CNPJ", "PHONE", "EMAIL" ou "RANDOM"); documento (opcional, CPF/CNPJ do recebedor); webhook (opcional, até 255 caracteres).
Resposta 200:
```json
{"status": "success", "message": "Saque enviado para processamento automatico.", "internalreference": "7d9f2c1a-4b3e-4f6a-9c8d-1e2f3a4b5c6d", "valores": {"solicitado": 500, "taxa": 7.5, "valor_liquido": 500, "total_debitado": 507.5}}
```
- Guarde internalreference: é o idTransaction dos webhooks de saque.
- HTTP 200 com "status": "error" = recusado: "Campos invalidos ou ausentes." (com "erros") ou "Saldo insuficiente para cobrir o valor do saque + taxas." (com "detalhes"). Sempre confira status == "success".
- Se o saldo cobre o valor mas não valor + taxa, a taxa sai do valor enviado (valor_liquido = valor − taxa; total_debitado = valor).
- Outros erros: 403 (IP, escopo, saque desabilitado, conta), 422 {"status": "error", "message": "Valor maximo por saque excedido"} e outros limites, 503 (saques temporariamente indisponíveis), 409, 429, 500.
## POST /saldo — saldo da conta (escopo saldo; exige o mesmo IP liberado do saque)
```json
{"status": "success", "data": {"account_id": "1042", "username": "minhaloja", "moeda": "BRL", "disponivel": 1523.47, "congelado": 0, "congelado_med": 0, "total_vendas": 98420.1, "total_saques": 96890, "consultado_em": "2026-10-05T17:05:00.000Z"}}
```
## Erros
- Formatos: {"error": "..."} ou {"status": "error", "message": "..."}; validação da cobrança: {"error": "Dados invalidos", "detalhes": []}.
- 400 requisição malformada · 401 api_key ausente/inválida/revogada · 403 conta, escopo, permissão ou IP · 404 não encontrado · 409 X-Idempotency-Key em processamento · 422 dados ou regra de negócio · 429 limite · 500/503 falha.
- 422 comuns na cobrança BRL: "Valor minimo por transacao e R$ 1,00", "Valor maximo por transacao excedido", "Documento do pagador deve conter 11 ou 14 digitos", "URL de webhook invalida ou nao permitida", "Split excede o teto de 90% do valor (maximo R$ ...)".
- Limites por minuto: cobrança 10.000 por api_key e 5.000 por IP; consulta 10.000 por IP; saque 100 por api_key e 60 por IP; saldo 60 por api_key e 120 por IP; geral 20.000 por IP. O contador por IP é único para todas as rotas e cada chamada soma 2. Em 429, espere o X-RateLimit-Reset (Unix, segundos).Revise o código que a IA gerar antes de colocar no ar e teste com R$ 1,00. Nunca cole a sua api_key real no chat da IA: use a variável de ambiente.
Prompts prontos
Escolha o seu caso, clique em Copiar prompt e cole na sua IA. Cada prompt já leva junto o Contexto da API — você só troca [SUA_STACK] e [URL_DO_SEU_WEBHOOK] pelos dados do seu projeto.
Os prompts mandam a IA guardar a api_key no servidor, perguntar se a sua plataforma é multi-cliente ou de 1 conta, confirmar o pagamento pela consulta, conferir a assinatura do webhook (só em 1 conta), usar X-Idempotency-Key e tratar os erros. Funcionam no Claude, ChatGPT, Cursor e Copilot.
Node.js + Express
Você é um(a) engenheiro(a) de software sênior. Integre a API de pagamentos NxGate (PIX) a um projeto Node.js + Express.
ANTES de escrever qualquer código, me faça esta pergunta e espere a minha resposta:
"A sua plataforma é multi-cliente (cada cliente cadastra a própria api_key da NxGate) ou usa 1 conta só (uma única api_key, a sua)?"
- 1 conta: siga todas as regras abaixo, inclusive a validação da assinatura HMAC do webhook.
- Multi-cliente: NÃO implemente a validação HMAC — ignore os headers X-NXGate-Timestamp e X-NXGate-Signature e não peça NXGATE_WEBHOOK_SECRET. A confirmação é só pela consulta: localize o pedido pelo identificador do webhook, descubra de qual cliente ele é e chame POST /v2/consultar com a api_key DESSE cliente; libere só se "status" for "paid". Guarde a api_key de cada cliente criptografada no servidor, nunca no front-end.
Meu projeto: [SUA_STACK] (versões, banco de dados, ORM)
URL pública do webhook: [URL_DO_SEU_WEBHOOK]
Construa:
1. src/nxgate.js — cliente da API no servidor: criarCobranca({ valor, pedidoId }) chama POST /v2/cobranca com api_key, valor, webhook = [URL_DO_SEU_WEBHOOK], magic_id = pedidoId e header X-Idempotency-Key; consultarCobranca(idTransaction) chama POST /v2/consultar. Use fetch nativo com timeout.
2. Rota POST /checkout/pix: cria a cobrança, salva idTransaction e a X-Idempotency-Key no pedido e devolve paymentCode e um QR Code gerado a partir do paymentCode (biblioteca qrcode). Não use o paymentCodeBase64 como imagem.
3. Rota do webhook (caminho de [URL_DO_SEU_WEBHOOK]) com express.raw({ type: "*/*" }) para ter o corpo bruto: valida a assinatura (só se for 1 conta), responde 200 e processa:
- "QR_CODE_COPY_AND_PASTE_PAID": consulta /v2/consultar com data.tx_id e, se "paid", marca o pedido como pago (só se ainda não estiver).
- status "pending" (sem type) ou evento desconhecido: só registra.
4. Rota GET /pedidos/:id/status para a tela de "aguardando pagamento" consultar o SEU backend (o navegador nunca chama a NxGate).
5. Reconciliação a cada 5 minutos: pedidos pendentes há mais de 2 minutos são consultados em /v2/consultar.
Regras obrigatórias:
- A api_key fica só no servidor, em variável de ambiente (NXGATE_API_KEY). Nunca no front-end, no app, em log ou no repositório.
- Não confie só no webhook: antes de liberar o pedido, chame POST /v2/consultar com o data.tx_id recebido e libere apenas se "status" for "paid".
- Só se for 1 conta: valide a assinatura do webhook: HMAC-SHA256 de "{X-NXGate-Timestamp}.{corpo bruto}" com NXGATE_WEBHOOK_SECRET, comparado em tempo constante com X-NXGate-Signature ("sha256=<hex>"); recuse timestamp com mais de 5 minutos de diferença.
- Envie X-Idempotency-Key (UUID v4) ao criar a cobrança; guarde a chave no pedido e reuse só para repetir a MESMA cobrança.
- Processe o webhook de forma idempotente (o mesmo evento pode chegar de novo) e responda 2xx em menos de 12 segundos; trabalho pesado vai para fila.
- Trate os erros: HTTP 401/403/409/422/429/5xx, com mensagem em "error" ou "message". Mostre mensagens amigáveis ao cliente e registre o detalhe no log (sem a api_key).
- O webhook não tem reenvio automático: crie uma rotina de reconciliação que consulta os pedidos pendentes.
Entregue o código completo e comentado, separado por arquivo, com as variáveis de ambiente necessárias e um roteiro de teste no sandbox da NxGate (https://sandbox.nxgate.com.br com a chave de teste nxg_test_…, simulando cada resultado com POST /v2/sandbox/simular) e depois um teste real com R$ 1,00 em produção. Deixe a URL base e a api_key em variáveis de ambiente, para trocar do sandbox para a produção sem mexer no código. A especificação completa da API NxGate está no fim desta mensagem — siga exatamente os nomes de campos e eventos dela.PHP + Laravel
Você é um(a) engenheiro(a) de software sênior. Integre a API de pagamentos NxGate (PIX) a um projeto Laravel.
ANTES de escrever qualquer código, me faça esta pergunta e espere a minha resposta:
"A sua plataforma é multi-cliente (cada cliente cadastra a própria api_key da NxGate) ou usa 1 conta só (uma única api_key, a sua)?"
- 1 conta: siga todas as regras abaixo, inclusive a validação da assinatura HMAC do webhook.
- Multi-cliente: NÃO implemente a validação HMAC — ignore os headers X-NXGate-Timestamp e X-NXGate-Signature e não peça NXGATE_WEBHOOK_SECRET. A confirmação é só pela consulta: localize o pedido pelo identificador do webhook, descubra de qual cliente ele é e chame POST /v2/consultar com a api_key DESSE cliente; libere só se "status" for "paid". Guarde a api_key de cada cliente criptografada no servidor, nunca no front-end.
Meu projeto: [SUA_STACK] (versão do Laravel/PHP, banco, filas)
URL pública do webhook: [URL_DO_SEU_WEBHOOK]
Construa:
1. config/services.php com 'nxgate' => ['key' => env('NXGATE_API_KEY'), 'webhook_secret' => env('NXGATE_WEBHOOK_SECRET')] e a classe App\Services\NxGate (Http facade, timeout) com criarCobranca(Pedido $pedido) — POST /v2/cobranca com api_key, valor, webhook = [URL_DO_SEU_WEBHOOK], magic_id = id do pedido e header X-Idempotency-Key salvo no pedido — e consultar(string $idTransaction) — POST /v2/consultar.
2. Controller de checkout: cria a cobrança, salva idTransaction no pedido e mostra o paymentCode (com botão copiar) e um QR Code gerado a partir dele (ex.: simplesoftwareio/simple-qrcode). Não use o paymentCodeBase64 como imagem.
3. Rota POST do webhook em routes/api.php (sem CSRF), no caminho de [URL_DO_SEU_WEBHOOK]: valida a assinatura (só se for 1 conta) com $request->getContent() (corpo bruto), despacha um Job e responde 200. O Job, para "QR_CODE_COPY_AND_PASTE_PAID", consulta /v2/consultar com data.tx_id e marca o pedido como pago só se vier "paid" e ele ainda não estiver pago (use transação/lock).
4. Comando agendado de reconciliação a cada 5 minutos (no Laravel 11+, em routes/console.php) para pedidos pendentes há mais de 2 minutos.
Regras obrigatórias:
- A api_key fica só no servidor, em variável de ambiente (NXGATE_API_KEY). Nunca no front-end, no app, em log ou no repositório.
- Não confie só no webhook: antes de liberar o pedido, chame POST /v2/consultar com o data.tx_id recebido e libere apenas se "status" for "paid".
- Só se for 1 conta: valide a assinatura do webhook: HMAC-SHA256 de "{X-NXGate-Timestamp}.{corpo bruto}" com NXGATE_WEBHOOK_SECRET, comparado em tempo constante com X-NXGate-Signature ("sha256=<hex>"); recuse timestamp com mais de 5 minutos de diferença.
- Envie X-Idempotency-Key (UUID v4) ao criar a cobrança; guarde a chave no pedido e reuse só para repetir a MESMA cobrança.
- Processe o webhook de forma idempotente (o mesmo evento pode chegar de novo) e responda 2xx em menos de 12 segundos; trabalho pesado vai para fila.
- Trate os erros: HTTP 401/403/409/422/429/5xx, com mensagem em "error" ou "message". Mostre mensagens amigáveis ao cliente e registre o detalhe no log (sem a api_key).
- O webhook não tem reenvio automático: crie uma rotina de reconciliação que consulta os pedidos pendentes.
Entregue o código completo e comentado, separado por arquivo, com as variáveis de ambiente necessárias e um roteiro de teste no sandbox da NxGate (https://sandbox.nxgate.com.br com a chave de teste nxg_test_…, simulando cada resultado com POST /v2/sandbox/simular) e depois um teste real com R$ 1,00 em produção. Deixe a URL base e a api_key em variáveis de ambiente, para trocar do sandbox para a produção sem mexer no código. A especificação completa da API NxGate está no fim desta mensagem — siga exatamente os nomes de campos e eventos dela.Python (FastAPI ou Django)
Você é um(a) engenheiro(a) de software sênior. Integre a API de pagamentos NxGate (PIX) a um projeto Python.
ANTES de escrever qualquer código, me faça esta pergunta e espere a minha resposta:
"A sua plataforma é multi-cliente (cada cliente cadastra a própria api_key da NxGate) ou usa 1 conta só (uma única api_key, a sua)?"
- 1 conta: siga todas as regras abaixo, inclusive a validação da assinatura HMAC do webhook.
- Multi-cliente: NÃO implemente a validação HMAC — ignore os headers X-NXGate-Timestamp e X-NXGate-Signature e não peça NXGATE_WEBHOOK_SECRET. A confirmação é só pela consulta: localize o pedido pelo identificador do webhook, descubra de qual cliente ele é e chame POST /v2/consultar com a api_key DESSE cliente; libere só se "status" for "paid". Guarde a api_key de cada cliente criptografada no servidor, nunca no front-end.
Meu projeto: [SUA_STACK] (FastAPI ou Django, versão, banco, fila)
URL pública do webhook: [URL_DO_SEU_WEBHOOK]
Construa:
1. nxgate.py — cliente da API (httpx com timeout): criar_cobranca(pedido) chama POST /v2/cobranca com api_key (os.environ["NXGATE_API_KEY"]), valor, webhook = [URL_DO_SEU_WEBHOOK], magic_id = id do pedido e header X-Idempotency-Key; consultar(id_transaction) chama POST /v2/consultar.
2. Endpoint de checkout: cria a cobrança, salva idTransaction e a X-Idempotency-Key no pedido e devolve paymentCode e um QR Code gerado a partir dele (biblioteca qrcode). Não use o paymentCodeBase64 como imagem.
3. Endpoint do webhook no caminho de [URL_DO_SEU_WEBHOOK], lendo o corpo bruto (FastAPI: await request.body(); Django: request.body, com csrf_exempt): valida a assinatura (só se for 1 conta), responde 200 e processa em segundo plano:
- "QR_CODE_COPY_AND_PASTE_PAID": consulta /v2/consultar com data.tx_id e, se "paid", marca o pedido como pago (só se ainda não estiver; use transação/select_for_update).
- status "pending" (sem type) ou evento desconhecido: só registra.
4. Tarefa periódica de reconciliação a cada 5 minutos (Celery beat, APScheduler ou cron) para pedidos pendentes há mais de 2 minutos.
Regras obrigatórias:
- A api_key fica só no servidor, em variável de ambiente (NXGATE_API_KEY). Nunca no front-end, no app, em log ou no repositório.
- Não confie só no webhook: antes de liberar o pedido, chame POST /v2/consultar com o data.tx_id recebido e libere apenas se "status" for "paid".
- Só se for 1 conta: valide a assinatura do webhook: HMAC-SHA256 de "{X-NXGate-Timestamp}.{corpo bruto}" com NXGATE_WEBHOOK_SECRET, comparado em tempo constante com X-NXGate-Signature ("sha256=<hex>"); recuse timestamp com mais de 5 minutos de diferença.
- Envie X-Idempotency-Key (UUID v4) ao criar a cobrança; guarde a chave no pedido e reuse só para repetir a MESMA cobrança.
- Processe o webhook de forma idempotente (o mesmo evento pode chegar de novo) e responda 2xx em menos de 12 segundos; trabalho pesado vai para fila.
- Trate os erros: HTTP 401/403/409/422/429/5xx, com mensagem em "error" ou "message". Mostre mensagens amigáveis ao cliente e registre o detalhe no log (sem a api_key).
- O webhook não tem reenvio automático: crie uma rotina de reconciliação que consulta os pedidos pendentes.
Entregue o código completo e comentado, separado por arquivo, com as variáveis de ambiente necessárias e um roteiro de teste no sandbox da NxGate (https://sandbox.nxgate.com.br com a chave de teste nxg_test_…, simulando cada resultado com POST /v2/sandbox/simular) e depois um teste real com R$ 1,00 em produção. Deixe a URL base e a api_key em variáveis de ambiente, para trocar do sandbox para a produção sem mexer no código. A especificação completa da API NxGate está no fim desta mensagem — siga exatamente os nomes de campos e eventos dela.Next.js
Você é um(a) engenheiro(a) de software sênior. Integre a API de pagamentos NxGate (PIX) a um projeto Next.js com App Router.
ANTES de escrever qualquer código, me faça esta pergunta e espere a minha resposta:
"A sua plataforma é multi-cliente (cada cliente cadastra a própria api_key da NxGate) ou usa 1 conta só (uma única api_key, a sua)?"
- 1 conta: siga todas as regras abaixo, inclusive a validação da assinatura HMAC do webhook.
- Multi-cliente: NÃO implemente a validação HMAC — ignore os headers X-NXGate-Timestamp e X-NXGate-Signature e não peça NXGATE_WEBHOOK_SECRET. A confirmação é só pela consulta: localize o pedido pelo identificador do webhook, descubra de qual cliente ele é e chame POST /v2/consultar com a api_key DESSE cliente; libere só se "status" for "paid". Guarde a api_key de cada cliente criptografada no servidor, nunca no front-end.
Meu projeto: [SUA_STACK] (versão do Next.js, banco/ORM, onde roda)
URL pública do webhook: [URL_DO_SEU_WEBHOOK]
Construa:
1. lib/nxgate.ts com import "server-only": criarCobranca e consultarCobranca chamando POST /v2/cobranca (api_key, valor, webhook = [URL_DO_SEU_WEBHOOK], magic_id = id do pedido, header X-Idempotency-Key) e POST /v2/consultar. A chave vem de process.env.NXGATE_API_KEY — nunca use prefixo NEXT_PUBLIC_ nem chame a NxGate de componente cliente.
2. Route handler app/api/pix/route.ts (POST): cria a cobrança, salva idTransaction no pedido e devolve paymentCode. Um componente cliente mostra o copia-e-cola e o QR gerado a partir do paymentCode (biblioteca qrcode) e consulta o status pelo SEU route handler app/api/pedidos/[id]/route.ts.
3. Route handler do webhook (caminho de [URL_DO_SEU_WEBHOOK]) com export const runtime = "nodejs": lê o corpo bruto com await request.text(), valida a assinatura (só se for 1 conta) e, para "QR_CODE_COPY_AND_PASTE_PAID", consulta /v2/consultar com data.tx_id e marca o pedido como pago só com "paid" (idempotente). Responda 200 rápido.
4. Reconciliação a cada 5 minutos (cron da plataforma chamando uma rota protegida) para pedidos pendentes há mais de 2 minutos.
Regras obrigatórias:
- A api_key fica só no servidor, em variável de ambiente (NXGATE_API_KEY). Nunca no front-end, no app, em log ou no repositório.
- Não confie só no webhook: antes de liberar o pedido, chame POST /v2/consultar com o data.tx_id recebido e libere apenas se "status" for "paid".
- Só se for 1 conta: valide a assinatura do webhook: HMAC-SHA256 de "{X-NXGate-Timestamp}.{corpo bruto}" com NXGATE_WEBHOOK_SECRET, comparado em tempo constante com X-NXGate-Signature ("sha256=<hex>"); recuse timestamp com mais de 5 minutos de diferença.
- Envie X-Idempotency-Key (UUID v4) ao criar a cobrança; guarde a chave no pedido e reuse só para repetir a MESMA cobrança.
- Processe o webhook de forma idempotente (o mesmo evento pode chegar de novo) e responda 2xx em menos de 12 segundos; trabalho pesado vai para fila.
- Trate os erros: HTTP 401/403/409/422/429/5xx, com mensagem em "error" ou "message". Mostre mensagens amigáveis ao cliente e registre o detalhe no log (sem a api_key).
- O webhook não tem reenvio automático: crie uma rotina de reconciliação que consulta os pedidos pendentes.
Entregue o código completo e comentado, separado por arquivo, com as variáveis de ambiente necessárias e um roteiro de teste no sandbox da NxGate (https://sandbox.nxgate.com.br com a chave de teste nxg_test_…, simulando cada resultado com POST /v2/sandbox/simular) e depois um teste real com R$ 1,00 em produção. Deixe a URL base e a api_key em variáveis de ambiente, para trocar do sandbox para a produção sem mexer no código. A especificação completa da API NxGate está no fim desta mensagem — siga exatamente os nomes de campos e eventos dela.WordPress + WooCommerce
Você é um(a) engenheiro(a) de software sênior. Crie um plugin WordPress que adiciona a NxGate (PIX) como forma de pagamento no WooCommerce.
ANTES de escrever qualquer código, me faça esta pergunta e espere a minha resposta:
"A sua plataforma é multi-cliente (cada cliente cadastra a própria api_key da NxGate) ou usa 1 conta só (uma única api_key, a sua)?"
- 1 conta: siga todas as regras abaixo, inclusive a validação da assinatura HMAC do webhook.
- Multi-cliente: NÃO implemente a validação HMAC — ignore os headers X-NXGate-Timestamp e X-NXGate-Signature e não peça NXGATE_WEBHOOK_SECRET. A confirmação é só pela consulta: localize o pedido pelo identificador do webhook, descubra de qual cliente ele é e chame POST /v2/consultar com a api_key DESSE cliente; libere só se "status" for "paid". Guarde a api_key de cada cliente criptografada no servidor, nunca no front-end.
Meu projeto: [SUA_STACK] (versões do WordPress, WooCommerce e PHP; tema)
URL pública do webhook: [URL_DO_SEU_WEBHOOK] (sugestão: /wp-json/nxgate/v1/webhook)
Construa:
1. Classe que estende WC_Payment_Gateway ("PIX NxGate"). A api_key e o segredo de assinatura ficam no servidor, em constantes no wp-config.php (NXGATE_API_KEY e NXGATE_WEBHOOK_SECRET) — não salve a chave no banco nem mostre na tela de configurações.
2. process_payment: chama POST /v2/cobranca com wp_remote_post (timeout 30), api_key, valor do pedido, webhook = [URL_DO_SEU_WEBHOOK], magic_id = ID do pedido e header X-Idempotency-Key guardado no meta do pedido; salva idTransaction no meta, deixa o pedido "aguardando pagamento" e leva o cliente à página de obrigado.
3. Página de obrigado: mostra o PIX copia-e-cola (paymentCode) com botão copiar e um QR Code gerado a partir dele; verifica o status pelo próprio WordPress (admin-ajax ou REST), nunca chamando a NxGate do navegador.
4. Rota REST register_rest_route('nxgate/v1', '/webhook') (permission_callback público): valida a assinatura (só se for 1 conta) com $request->get_body() (corpo bruto); para "QR_CODE_COPY_AND_PASTE_PAID", localiza o pedido pelo meta idTransaction (data.tx_id) ou por data.magic_id, consulta /v2/consultar e chama $order->payment_complete() só se vier "paid" e o pedido ainda não estiver pago. Responda 200.
5. WP-Cron a cada 5 minutos para reconciliar pedidos "aguardando pagamento" com cobrança NxGate.
Regras obrigatórias:
- A api_key fica só no servidor, em variável de ambiente (NXGATE_API_KEY). Nunca no front-end, no app, em log ou no repositório.
- Não confie só no webhook: antes de liberar o pedido, chame POST /v2/consultar com o data.tx_id recebido e libere apenas se "status" for "paid".
- Só se for 1 conta: valide a assinatura do webhook: HMAC-SHA256 de "{X-NXGate-Timestamp}.{corpo bruto}" com NXGATE_WEBHOOK_SECRET, comparado em tempo constante com X-NXGate-Signature ("sha256=<hex>"); recuse timestamp com mais de 5 minutos de diferença.
- Envie X-Idempotency-Key (UUID v4) ao criar a cobrança; guarde a chave no pedido e reuse só para repetir a MESMA cobrança.
- Processe o webhook de forma idempotente (o mesmo evento pode chegar de novo) e responda 2xx em menos de 12 segundos; trabalho pesado vai para fila.
- Trate os erros: HTTP 401/403/409/422/429/5xx, com mensagem em "error" ou "message". Mostre mensagens amigáveis ao cliente e registre o detalhe no log (sem a api_key).
- O webhook não tem reenvio automático: crie uma rotina de reconciliação que consulta os pedidos pendentes.
Entregue o código completo e comentado, separado por arquivo, com as variáveis de ambiente necessárias e um roteiro de teste no sandbox da NxGate (https://sandbox.nxgate.com.br com a chave de teste nxg_test_…, simulando cada resultado com POST /v2/sandbox/simular) e depois um teste real com R$ 1,00 em produção. Deixe a URL base e a api_key em variáveis de ambiente, para trocar do sandbox para a produção sem mexer no código. A especificação completa da API NxGate está no fim desta mensagem — siga exatamente os nomes de campos e eventos dela.Gerar PIX + webhook + consulta (qualquer stack)
Você é um(a) engenheiro(a) de software sênior. Implemente o fluxo completo de cobrança PIX da NxGate no meu projeto: gerar o PIX, receber o webhook e confirmar o pagamento.
ANTES de escrever qualquer código, me faça esta pergunta e espere a minha resposta:
"A sua plataforma é multi-cliente (cada cliente cadastra a própria api_key da NxGate) ou usa 1 conta só (uma única api_key, a sua)?"
- 1 conta: siga todas as regras abaixo, inclusive a validação da assinatura HMAC do webhook.
- Multi-cliente: NÃO implemente a validação HMAC — ignore os headers X-NXGate-Timestamp e X-NXGate-Signature e não peça NXGATE_WEBHOOK_SECRET. A confirmação é só pela consulta: localize o pedido pelo identificador do webhook, descubra de qual cliente ele é e chame POST /v2/consultar com a api_key DESSE cliente; libere só se "status" for "paid". Guarde a api_key de cada cliente criptografada no servidor, nunca no front-end.
Meu projeto: [SUA_STACK]
URL pública do webhook: [URL_DO_SEU_WEBHOOK]
Construa:
1. Um cliente da API no servidor com:
- criarCobranca: POST https://api.nxgate.com.br/v2/cobranca com api_key, valor (decimal em reais), webhook = [URL_DO_SEU_WEBHOOK], magic_id = ID do pedido e header X-Idempotency-Key.
- consultarCobranca: POST https://api.nxgate.com.br/v2/consultar com api_key e transaction_id.
2. Checkout: cria a cobrança, salva idTransaction e a X-Idempotency-Key no pedido e mostra ao cliente o paymentCode (copia-e-cola, com botão de copiar) e um QR Code gerado a partir do paymentCode. Não use o paymentCodeBase64 como imagem: conforme a rede, ele vem como PNG ou como o próprio código em base64.
3. Endpoint do webhook em [URL_DO_SEU_WEBHOOK]: lê o corpo bruto, valida a assinatura (só se for 1 conta), responde 200 rápido e processa:
- type "QR_CODE_COPY_AND_PASTE_PAID": consulta /v2/consultar com data.tx_id e, se "paid", marca o pedido como pago (só se ainda não estiver).
- status "pending" (sem type): só registra.
- qualquer outro: ignora com 200.
4. Tela de "aguardando pagamento" que pergunta o status ao SEU backend (o navegador nunca chama a NxGate).
5. Reconciliação a cada 5 minutos: consulta os pedidos pendentes criados há mais de 2 minutos. Se a consulta seguir "waiting" depois do webhook de pago, o pagamento está em análise: não cobre de novo.
Regras obrigatórias:
- A api_key fica só no servidor, em variável de ambiente (NXGATE_API_KEY). Nunca no front-end, no app, em log ou no repositório.
- Não confie só no webhook: antes de liberar o pedido, chame POST /v2/consultar com o data.tx_id recebido e libere apenas se "status" for "paid".
- Só se for 1 conta: valide a assinatura do webhook: HMAC-SHA256 de "{X-NXGate-Timestamp}.{corpo bruto}" com NXGATE_WEBHOOK_SECRET, comparado em tempo constante com X-NXGate-Signature ("sha256=<hex>"); recuse timestamp com mais de 5 minutos de diferença.
- Envie X-Idempotency-Key (UUID v4) ao criar a cobrança; guarde a chave no pedido e reuse só para repetir a MESMA cobrança.
- Processe o webhook de forma idempotente (o mesmo evento pode chegar de novo) e responda 2xx em menos de 12 segundos; trabalho pesado vai para fila.
- Trate os erros: HTTP 401/403/409/422/429/5xx, com mensagem em "error" ou "message". Mostre mensagens amigáveis ao cliente e registre o detalhe no log (sem a api_key).
- O webhook não tem reenvio automático: crie uma rotina de reconciliação que consulta os pedidos pendentes.
Entregue o código completo e comentado, separado por arquivo, com as variáveis de ambiente necessárias e um roteiro de teste no sandbox da NxGate (https://sandbox.nxgate.com.br com a chave de teste nxg_test_…, simulando cada resultado com POST /v2/sandbox/simular) e depois um teste real com R$ 1,00 em produção. Deixe a URL base e a api_key em variáveis de ambiente, para trocar do sandbox para a produção sem mexer no código. A especificação completa da API NxGate está no fim desta mensagem — siga exatamente os nomes de campos e eventos dela.Saque PIX
Você é um(a) engenheiro(a) de software sênior. Implemente o saque PIX da NxGate (cash-out) no meu projeto, com o recebimento dos webhooks de saque.
ANTES de escrever qualquer código, me faça esta pergunta e espere a minha resposta:
"A sua plataforma é multi-cliente (cada cliente cadastra a própria api_key da NxGate) ou usa 1 conta só (uma única api_key, a sua)?"
- 1 conta: siga todas as regras abaixo, inclusive a validação da assinatura HMAC do webhook.
- Multi-cliente: NÃO implemente a validação HMAC — ignore os headers X-NXGate-Timestamp e X-NXGate-Signature e não peça NXGATE_WEBHOOK_SECRET. A validação é só pelo idTransaction: ele precisa ser de um saque ainda não finalizado de algum cliente seu (se não achar, ignore com 200). Guarde a api_key de cada cliente criptografada no servidor, nunca no front-end.
Meu projeto: [SUA_STACK]
URL pública do webhook: [URL_DO_SEU_WEBHOOK]
Contexto: o saque só funciona a partir de um IP cadastrado em Painel → API → IP Seguro para Saques (senão 403 "IP nao autorizado."). A chave precisa do escopo cashout (ou full).
Construa:
1. solicitarSaque({ valor, chavePix, tipoChave, documento }): POST https://api.nxgate.com.br/pix/sacar com api_key, valor (decimal em reais), chave_pix, tipo_chave ("CPF", "CNPJ", "PHONE", "EMAIL" ou "RANDOM"), webhook = [URL_DO_SEU_WEBHOOK] e header X-Idempotency-Key (UUID v4) gravado junto do saque antes da chamada.
2. Tratamento da resposta: HTTP 200 nem sempre é sucesso — só "status": "success" conta como saque aceito. "status": "error" com "Saldo insuficiente..." (traz "detalhes") ou "Campos invalidos ou ausentes." (traz "erros") deve virar erro claro. Guarde internalreference e valores (solicitado, taxa, valor_liquido, total_debitado).
3. Endpoint do webhook em [URL_DO_SEU_WEBHOOK]: lê o corpo bruto, valida a assinatura (só se for 1 conta), responde 200 e atualiza o saque pelo idTransaction (= internalreference):
- "PIX_CASHOUT_PAID": concluído.
- "PIX_CASHOUT_ERROR": falhou (motivo em "error"); se você debitou o usuário no seu sistema, devolva.
- "PIX_CASHOUT_REFUND": devolvido (motivo em "message"); pode chegar DEPOIS do PAID.
Na NxGate, em ERROR e REFUND o valor debitado volta sozinho para o saldo.
4. Opcional: consultar o saldo antes (POST /saldo, mesmo IP liberado, escopo saldo).
Regras obrigatórias:
- A api_key fica só no servidor, em variável de ambiente (NXGATE_API_KEY). Nunca no front-end, em log ou no repositório.
- Valide o webhook antes de mexer no saque: assinatura (só se for 1 conta; HMAC-SHA256 de "{X-NXGate-Timestamp}.{corpo bruto}" com NXGATE_WEBHOOK_SECRET, comparado em tempo constante com X-NXGate-Signature; recuse timestamp com mais de 5 minutos) e o idTransaction precisa ser de um saque seu ainda não finalizado — se não achar, ignore com 200. Como a API não tem rota de consulta de saque, essa conferência é a sua "consulta".
- Idempotência: nunca dispare o mesmo saque duas vezes. Em falha de rede, repita com a MESMA X-Idempotency-Key (vale 10 min); 409 significa que a primeira ainda está processando.
- Processe os webhooks de forma idempotente (podem chegar de novo) e responda 2xx em menos de 10 segundos.
- Trate os erros: 401/403 (chave, escopo, IP), 409, 422 (limites: mínimo R$ 1,00, máximo R$ 50.000,00 por saque), 429, 503 (saques temporariamente indisponíveis) e 500.
- Não existe rota para consultar um saque: guarde tudo e use os webhooks (e o Painel → Webhooks) para acompanhar.
Entregue o código completo e comentado, separado por arquivo, com as variáveis de ambiente necessárias e um roteiro de teste no sandbox da NxGate (https://sandbox.nxgate.com.br com a chave de teste nxg_test_…, simulando cada resultado com POST /v2/sandbox/simular) e depois um teste real com R$ 1,00 em produção. Deixe a URL base e a api_key em variáveis de ambiente, para trocar do sandbox para a produção sem mexer no código. A especificação completa da API NxGate está no fim desta mensagem — siga exatamente os nomes de campos e eventos dela.Cobrança em MXN (México)
Você é um(a) engenheiro(a) de software sênior. Implemente cobranças em pesos mexicanos (MXN) pela API v2 da NxGate no meu projeto.
ANTES de escrever qualquer código, me faça esta pergunta e espere a minha resposta:
"A sua plataforma é multi-cliente (cada cliente cadastra a própria api_key da NxGate) ou usa 1 conta só (uma única api_key, a sua)?"
- 1 conta: siga todas as regras abaixo, inclusive a validação da assinatura HMAC do webhook.
- Multi-cliente: NÃO implemente a validação HMAC — ignore os headers X-NXGate-Timestamp e X-NXGate-Signature e não peça NXGATE_WEBHOOK_SECRET. A confirmação é só pela consulta: localize o pedido pelo identificador do webhook, descubra de qual cliente ele é e chame POST /v2/consultar com a api_key DESSE cliente; libere só se "status" for "paid". Guarde a api_key de cada cliente criptografada no servidor, nunca no front-end.
Meu projeto: [SUA_STACK]
URL pública do webhook: [URL_DO_SEU_WEBHOOK]
Contexto: em MXN o meio de pagamento é o SPEI (transferência bancária instantânea). A API devolve uma CLABE de 18 dígitos na própria resposta; o comprador transfere o valor exato pelo app do banco. Valor por cobrança: de MX$ 20,00 a MX$ 5.000,00. Confirme no selo da documentação que a MXN está liberada (senão a API responde 422 "Moeda MXN ainda nao disponivel").
Construa:
1. criarCobrancaMXN(pedido): POST https://api.nxgate.com.br/v2/cobranca com api_key, "moeda": "MXN", "metodo": "SPEI", valor decimal em pesos (499.90 = MX$ 499,90 — não é em centavos), nome_pagador, email_pagador, webhook = [URL_DO_SEU_WEBHOOK], magic_id = ID do pedido e header X-Idempotency-Key.
2. Tela de pagamento: mostre paymentInstruction.clabe (o mesmo valor de paymentCode) com botão copiar, o valor exato em MX$ e a instrução "transfira pelo app do seu banco via SPEI". paymentCodeBase64 vem null em MXN. Se paymentInstruction.type vier diferente de "clabe" (ex.: "pendente" ou desconhecido), mostre o paymentCode quando houver e consulte /v2/consultar alguns segundos depois.
3. Webhook em [URL_DO_SEU_WEBHOOK]: é o mesmo QR_CODE_COPY_AND_PASTE_PAID do PIX, com data.currency = "MXN" e data.payment_method = "SPEI"; data.amount vem em pesos. Valide a assinatura (só se for 1 conta), consulte /v2/consultar com data.tx_id e marque como pago só com "paid".
4. Guarde os valores na moeda certa: moeda = data.currency ?? "BRL". O dinheiro em MXN é convertido para real no prazo de recebimento da conta; saques são sempre em reais.
Regras obrigatórias:
- A api_key fica só no servidor, em variável de ambiente (NXGATE_API_KEY). Nunca no front-end, no app, em log ou no repositório.
- Não confie só no webhook: antes de liberar o pedido, chame POST /v2/consultar com o data.tx_id recebido e libere apenas se "status" for "paid".
- Só se for 1 conta: valide a assinatura do webhook: HMAC-SHA256 de "{X-NXGate-Timestamp}.{corpo bruto}" com NXGATE_WEBHOOK_SECRET, comparado em tempo constante com X-NXGate-Signature ("sha256=<hex>"); recuse timestamp com mais de 5 minutos de diferença.
- Envie X-Idempotency-Key (UUID v4) ao criar a cobrança; guarde a chave no pedido e reuse só para repetir a MESMA cobrança.
- Processe o webhook de forma idempotente (o mesmo evento pode chegar de novo) e responda 2xx em menos de 12 segundos; trabalho pesado vai para fila.
- Trate os erros: HTTP 401/403/409/422/429/5xx, com mensagem em "error" ou "message". Mostre mensagens amigáveis ao cliente e registre o detalhe no log (sem a api_key).
- O webhook não tem reenvio automático: crie uma rotina de reconciliação que consulta os pedidos pendentes.
Entregue o código completo e comentado, separado por arquivo, com as variáveis de ambiente necessárias e um roteiro de teste no sandbox da NxGate (https://sandbox.nxgate.com.br com a chave de teste nxg_test_…, simulando cada resultado com POST /v2/sandbox/simular) e depois um teste real com R$ 1,00 em produção. Deixe a URL base e a api_key em variáveis de ambiente, para trocar do sandbox para a produção sem mexer no código. A especificação completa da API NxGate está no fim desta mensagem — siga exatamente os nomes de campos e eventos dela.