NervaNervaAPI Docs
OnlineREST + JSON

Nerva API Documentação

Integre pagamentos PIX na sua plataforma. Gere cobranças, acompanhe pagamentos em tempo real e gerencie saques com poucas linhas de código.

Base URLhttps://pixnerva.com.br/api

1. Autenticação

Todas as requisições autenticadas devem incluir sua API Key no header x-api-key. A chave é gerada no painel do seller em Integrações > API Keys.

Header de autenticação
# Inclua em todas as requisições autenticadas
x-api-key: sk_live_a1b2c3d4e5f6g7h8i9j0...

Segurança: Nunca exponha sua API Key no frontend ou em repositórios públicos. Use variáveis de ambiente no servidor.

2. Criar Cobrança PIX

Cria uma cobrança PIX instantânea. Retorna o código PIX “Copia e Cola” e o QR Code para o pagador. A cobrança expira em 24 horas por padrão (configurável via expirationInSeconds).

POST/sales

Parâmetros do Body

CampoTipoObrigatórioDescrição
amountnumberSIMValor em reais (ex: 100.00). Mínimo R$ 0,01 / Máximo R$ 10.000,00
customerobjectSIMDados do pagador (objeto aninhado — ver campos abaixo)
customer.documentstringSIMCPF ou CNPJ do pagador (único campo obrigatório do customer) — obrigatório, exceto contas habilitadas para pagador/recebedor
customer.namestringnãoNome completo do pagador
customer.emailstringnãoEmail do pagador
customer.phonestringnãoTelefone do pagador
descriptionstringnãoDescrição da cobrança
itemsarraynãoItens da cobrança [{description, quantity, unitPrice, tangible}]
expirationInSecondsnumbernãoExpiração do PIX em segundos (300–86400, padrão: 86400)
postbackUrlstringnãoURL HTTPS para receber webhooks desta venda. Quando enviado, substitui os webhooks cadastrados no painel apenas para esta transação. Apenas https://, IPs privados e localhost são bloqueados.
externalIdstringnãoReferência do pedido no SEU sistema (ex: número do pedido). Ecoada na resposta e em todos os webhooks desta venda, pra você reconciliar com o seu backend.
Request
POST /sales
x-api-key: sk_live_...
Content-Type: application/json

{
  "amount": 100.00,
  "description": "Plano Premium",
  "expirationInSeconds": 1800,
  "customer": {
    "name": "João Silva",
    "document": "12345678901",
    "email": "joao@email.com",
    "phone": "11999999999"
  },
  "items": [
    { "description": "Plano Premium", "quantity": 1, "unitPrice": 100.00 }
  ],
  "postbackUrl": "https://meucheckout.com/webhooks/pagamentos"
}
Response (201)
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "pending",
  "amount": 100.00,
  "fee": 8.98,
  "netAmount": 91.02,
  "pixCode": "00020101021226580014br.gov.bcb...",
  "pixQrCode": "https://pixnerva.com.br/api/...",
  "transactionId": "gw_tx_abc123"
}

Cálculo das taxas: fee = (amount x 6.99%) + R$ 1.99 | netAmount = amount - fee

Exemplo: R$ 100,00 → fee = R$ 8,98 → netAmount = R$ 91,02

Idempotência

Para evitar cobranças duplicadas em caso de retentativa de rede ou timeout, envie o header idempotency-key ao criar uma cobrança. Se a chave já foi processada, a API devolve a resposta original (mesmo PIX, mesmo ID) sem criar nova cobrança.

Recomendado: use o ID interno do pedido no seu sistema (ex: order-12345) ou um UUID v4. A chave é válida por 24 horas — depois disso a mesma chave gera nova cobrança.

Request com idempotency-key
POST /sales
x-api-key: sk_live_...
idempotency-key: pedido-12345
Content-Type: application/json

{
  "amount": 100.00,
  "customer": { "document": "12345678901" }
}

Importante: sempre que sua aplicação retentar uma criação de cobrança após um timeout, mande a MESMA idempotency-key. Sem ela, o seller pode acabar gerando 2 PIX cobrando o mesmo cliente.

Tracking de Marketing

Inclua o objeto tracking no body do POST /sales com UTMs, click IDs e cookies do pixel. Quando a venda for paga, a plataforma dispara automaticamente eventos para a Meta Conversions API e TikTok Events API (se a integração estiver ativa no painel do seller). Tudo opcional.

Campos do objeto tracking

CampoDescrição
utmSourceUTM Source (ex: facebook, google, tiktok)
utmMediumUTM Medium (ex: cpc, social)
utmCampaignUTM Campaign (nome da campanha)
utmContentUTM Content (variação do criativo)
utmTermUTM Term (palavra-chave)
fbclidClick ID do Facebook Ads (query param ?fbclid=...)
ttclidClick ID do TikTok Ads
gclidClick ID do Google Ads
fbpCookie _fbp do Facebook Pixel (browser id)
fbcCookie _fbc do Facebook Pixel (click id)
clientUserAgentUser-Agent do navegador do cliente final
clientIpAddressIP do cliente final
eventIdEvent ID para deduplicar pixel + CAPI (se vazio, derivamos do sale.id)
Exemplo — POST /sales com tracking
{
  "amount": 100.00,
  "customer": { "document": "12345678901" },
  "tracking": {
    "utmSource": "facebook",
    "utmCampaign": "black-friday-2026",
    "fbclid": "IwAR2...",
    "fbp": "fb.1.1700000000000.123456789",
    "fbc": "fb.1.1700000000000.IwAR2...",
    "clientUserAgent": "Mozilla/5.0 (...)",
    "clientIpAddress": "201.10.20.30"
  }
}

Por quê: os browsers bloqueiam cada vez mais o Facebook Pixel, TikTok Pixel e Google Tag. Mandando esses dados pela API, a plataforma envia o evento server-to-server via Meta CAPI / TikTok Events API quando a venda for paga — recuperando atribuição de campanhas que o pixel sozinho perderia.

3. Consultar Cobrança

Retorna os detalhes completos de uma cobrança específica, incluindo o código PIX e o status do pagamento.

GET/sales/:id
Request
GET /sales/a1b2c3d4-e5f6-7890-abcd-ef1234567890
x-api-key: sk_live_...
Response (200)
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "paid",
  "amount": 100.00,
  "fee": 8.98,
  "netAmount": 91.02,
  "description": "Plano Premium",
  "pixCode": "00020101021226580014br.gov.bcb...",
  "pixQrCode": "https://pixnerva.com.br/api/...",
  "payerName": "João Silva",
  "payerEmail": "joao@email.com",
  "payerDocument": "12345678901",
  "payerPhone": "11999999999",
  "paymentMethod": "PIX",
  "transactionId": "gw_tx_abc123",
  "createdAt": "2026-03-23T10: 00: 00Z",
  "updatedAt": "2026-03-23T10: 05: 00Z"
}

Status possíveis: pending → paid | failed | refunded | expired

Disponível mediante habilitação: com status paid, a resposta inclui payer e receiver — ver Pagador e recebedor.

4. Listar Cobranças

Retorna a lista paginada de cobranças da sua empresa, com filtros por status, data e busca.

GET/sales?page=1&limit=20&status=paid

Parâmetros de Query

ParâmetroTipoDescrição
pagenumberPágina (padrão: 1)
limitnumberItens por página (padrão: 20)
statusstringFiltrar: pending, paid, failed, refunded, expired
startDatestringData inicial (ISO 8601)
endDatestringData final (ISO 8601)
searchstringBusca por nome, email ou documento
Response (200)
{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "status": "paid",
      "amount": 100.00,
      "fee": 8.98,
      "netAmount": 91.02,
      "payerName": "João Silva",
      "paymentMethod": "PIX",
      "createdAt": "2026-03-23T10: 00: 00Z"
    }
  ],
  "total": 142,
  "page": 1,
  "limit": 20
}

Produtos

O produto é o que você vende, independente de onde vende. Ele guarda duas coisas que o rastreio usa: os prazos de cada etapa da entrega e o conteúdo da área de membros, liberado quando o pedido é entregue. Cadastre uma vez por produto — não a cada pedido.

Disponibilidade: o módulo de loja é liberado por conta. Se os endpoints responderem 404, ele ainda não está ativo na sua — fale com suporte@pixnerva.com.br.

POST/products

Parâmetros do Body

CampoTipoObrigatórioDescrição
namestringSIMNome do produto (até 140 caracteres). É o que aparece como “Objeto” na página de rastreio.
externalRefstringnãoReferência do produto no SEU sistema (SKU, até 120 caracteres, único por conta). É este campo que vira o productRef no cadastro do rastreio — com ele você nunca precisa guardar o ID da plataforma.
trackingobjectnãoPrazos da timeline em horas contadas desde o início: postedAfterHours, inTransitAfterHours, outForDeliveryAfterHours e deliveredAfterHours. Sem ele valem os padrões da plataforma (24h, 72h, 168h e 192h).
descriptionstringnãoDescrição exibida no checkout (até 1000 caracteres)
coverUrlstringnãoURL https da imagem de capa (até 500 caracteres)
memberAreaobjectnãoConteúdo da área de membros ({ modules: [...] }). Sem ele o rastreio funciona normalmente e nada é liberado na entrega.
Request
POST /products
x-api-key: sk_live_...
Content-Type: application/json

{
  "name": "Kit Completo",
  "externalRef": "SKU-001",
  "tracking": {
    "postedAfterHours": 24,
    "inTransitAfterHours": 72,
    "outForDeliveryAfterHours": 168,
    "deliveredAfterHours": 192
  }
}
Response (201)
{
  "data": {
    "id": "9c81f2ab-4d5e-6789-abcd-ef0123456789",
    "name": "Kit Completo",
    "externalRef": "SKU-001",
    "description": null,
    "coverUrl": null,
    "tracking": {
      "postedAfterHours": 24,
      "inTransitAfterHours": 72,
      "outForDeliveryAfterHours": 168,
      "deliveredAfterHours": 192
    },
    "memberArea": {},
    "createdAt": "2026-09-24T12: 00: 00.000Z"
  }
}

Sobre o SKU: repetir um externalRef já usado responde 409 · não há busca de produto por SKU — GET /products não aceita filtro e o externalRef é resolvido internamente no cadastro do rastreio · se ainda assim quiser o ID da plataforma, liste os produtos uma vez e faça o de-para do seu lado · os prazos são fotografados quando o rastreio é criado: mudar o produto depois não altera pedidos em andamento.

Demais endpoints

MétodoRotaDescrição
GET/productsLista o catálogo da conta, com as ofertas de cada produto, no envelope { data, total, page, limit }
GET/products/:idDetalha um produto pelo ID da plataforma
PATCH/products/:idAtualiza os mesmos campos da criação — inclusive o externalRef
DELETE/products/:idRemove o produto

Quem casa com quem

São três identificadores, e cada um nasce num lugar. Usando os dois primeiros você nunca precisa guardar um ID da plataforma.

IdentificadorNasce emÉ usado de novo em
externalIdPOST /sales — o número do pedido no seu sistemaPOST /trackings, para localizar a venda
externalRefPOST /products — o seu SKUPOST /trackings, com o nome productRef
codePOST /trackings — o código da encomendaO comprador digita na página pública de rastreio

O fluxo inteiro: uma vez por produto, POST /products com externalRef e tracking · a cada pedido, POST /sales com externalId · quando o pagamento confirmar, POST /trackings com code, externalId e productRef.

Rastreio de Pedidos

Cadastre o código de rastreio de uma venda paga e o comprador acompanha a entrega numa timeline pública, com a sua marca. As etapas são fixas (Pedido confirmado → Postado → Em trânsito → Saiu para entrega → Entregue) e os tempos entre elas são configurados por produto — no painel ou pela API, na seção Produtos. Se o rastreio estiver vinculado a um produto com área de membros, o acesso do comprador é liberado automaticamente na entrega.

A venda não conhece o produto. O POST /sales não tem campo de produto: venda e produto só se encontram aqui, no cadastro do rastreio. Por isso esta chamada recebe os dois — a venda por saleId ou externalId, e o produto por productId ou productRef.

Disponibilidade: o módulo de loja é liberado por conta. Se os endpoints responderem 404, ele ainda não está ativo na sua — fale com suporte@pixnerva.com.br.

POST/trackings

Parâmetros do Body

CampoTipoObrigatórioDescrição
codestringSIMCódigo de rastreio exibido ao comprador (4-40 caracteres, letras/números/hífen — normalizado para maiúsculas)
saleIdstringnãoID da venda na plataforma. É obrigatório enviar saleId OU externalId.
externalIdstringnãoReferência do pedido no SEU sistema — o mesmo externalId enviado ao criar a cobrança. Alternativa ao saleId.
productIdstringnãoID do produto na plataforma — define os tempos da timeline e o conteúdo da área de membros
productRefstringnãoReferência do produto no SEU sistema (SKU). Alternativa ao productId pra quem integra via API.
startedAtstringnãoÂncora da timeline em ISO 8601 — a etapa “Pedido confirmado” acontece neste momento (padrão: agora)
Request
POST /trackings
x-api-key: sk_live_...
Content-Type: application/json

{
  "code": "BR123456789XX",
  "externalId": "pedido-1042",
  "productRef": "SKU-001"
}
Response (201)
{
  "data": {
    "id": "f3e2d1c0-b9a8-7654-3210-fedcba987654",
    "code": "BR123456789XX",
    "saleId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "9c81f2ab-...",
    "currentStage": "confirmed",
    "startedAt": "2026-09-23T14: 00: 00Z",
    "timeline": [
      { "stage": "confirmed", "label": "Pedido confirmado", "at": "2026-09-23T14: 00: 00Z", "reached": true, "current": true },
      { "stage": "posted", "label": "Postado", "at": "2026-09-24T14: 00: 00Z", "reached": false, "current": false },
      { "stage": "in_transit", "label": "Em trânsito", "at": "2026-09-26T14: 00: 00Z", "reached": false, "current": false },
      { "stage": "out_for_delivery", "label": "Saiu para entrega", "at": "2026-09-30T14: 00: 00Z", "reached": false, "current": false },
      { "stage": "delivered", "label": "Entregue", "at": "2026-10-01T14: 00: 00Z", "reached": false, "current": false }
    ]
  }
}

Regras: a venda precisa estar paid (400 caso contrário) · cada venda aceita um rastreio e cada código é único (409 em caso de duplicidade) · sem produto vinculado o rastreio funciona normalmente, mas não libera área de membros · os tempos da timeline são fotografados no cadastro — mudar a configuração do produto depois não altera rastreios em andamento.

Demais endpoints

MétodoRotaDescrição
GET/trackingsLista os rastreios da conta — paginação via page e limit; search busca por código de rastreio, número do pedido ou nome do comprador
GET/trackings/:idDetalha um rastreio, com currentStage e timeline computados
PATCH/trackings/:idAtualiza code, startedAt ou manualStage (override manual da etapa — só avança, nunca volta)
DELETE/trackings/:idRemove o rastreio

5. Consultar Saldo

Retorna o saldo total, o valor retido (retenção de segurança) e o saldo disponível para saque.

GET/withdrawals/balance
Request
GET /withdrawals/balance
x-api-key: sk_live_...
Response (200)
{
  "data": {
    "available": 150000,
    "withheld": 7500
  }
}
// Valores em centavos (150000 = R$ 1.500,00)

Retenção: 5% do valor líquido (netAmount) fica retido por 30 dias como proteção contra fraudes. Após o período, o valor é liberado automaticamente.

6. Solicitar Saque

Solicita um saque do saldo disponível. O saque passa por aprovação antes do processamento. Valor mínimo: R$ 1,00.

POST/withdrawals

Parâmetros do Body

CampoTipoObrigatórioDescrição
amountnumberSIMValor do saque em reais
methodstringSIM"pix"
pixKeyTypestringSIMTipo (lowercase): cpf, cnpj, email, phone, random
pixKeystringSIMChave PIX para receber o saque
referencestringnãoID de referência no seu sistema (devolvido nos webhooks)
Request
POST /withdrawals
x-api-key: sk_live_...
Content-Type: application/json

{
  "amount": 500.00,
  "method": "pix",
  "pixKeyType": "cpf",
  "pixKey": "12345678901",
  "reference": "pedido-saque-789"
}
Response (201)
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "amount": 500.00,
  "fee": 0,
  "status": "pending",
  "method": "pix",
  "pixKeyType": "cpf",
  "pixKey": "12345678901",
  "reference": "pedido-saque-789",
  "createdAt": "2026-03-23T14: 30: 00Z"
}

Fluxo: pending → approved → processing → completed | rejected | failed

Listar Saques

Lista os saques da sua conta com filtros e paginação.

GET/withdrawals/my-withdrawals

Query Parameters

CampoTipoDescrição
pagenumberPágina (default: 1)
limitnumberItens por página (máx: 100)
statusstringFiltrar: pending, approved, processing, completed, rejected, failed
Request
GET /withdrawals/my-withdrawals?page=1&limit=20&status=completed
x-api-key: sk_live_...
Response (200)
{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "amount": 500.00,
      "fee": 0,
      "status": "completed",
      "method": "pix",
      "pixKey": "12345678901",
      "reference": "pedido-saque-789",
      "createdAt": "2026-03-23T14: 30: 00Z"
    }
  ],
  "total": 15,
  "page": 1,
  "limit": 20
}

Buscar Saque por ID

Retorna os detalhes de um saque específico pelo ID.

GET/withdrawals/my-withdrawals/:id
Request
GET /withdrawals/my-withdrawals/a1b2c3d4-...
x-api-key: sk_live_...
Response (200)
{
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "amount": 500.00,
    "fee": 0,
    "status": "completed",
    "method": "pix",
    "pixKeyType": "cpf",
    "pixKey": "12345678901",
    "reference": "pedido-saque-789",
    "createdAt": "2026-03-23T14: 30: 00Z",
    "updatedAt": "2026-03-23T14: 35: 00Z"
  }
}

Disponível mediante habilitação: com status completed, este endpoint e o GET /cashout/:id incluem payer e receiver — ver Pagador e recebedor.

7. Webhooks

Configure uma URL de webhook no painel para receber notificações em tempo real quando o status de um pagamento mudar. A plataforma envia um POST com payload JSON assinado.

Webhook por transação (alternativa): envie o campo postbackUrl no body do POST /sales para receber as notificações dessa venda específica em uma URL diferente, sem precisar cadastrar nada no painel.

Quando enviado, postbackUrl substitui o webhook do painel apenas para aquela transação — os webhooks cadastrados continuam funcionando para todas as outras vendas. Apenas https:// é aceito; IPs privados, loopback e link-local são bloqueados. Mesma assinatura HMAC (x-pixnerva-signature) e mesma política de retentativas.

Headers enviados pela plataforma

HeaderDescrição
x-pixnerva-timestampTimestamp UNIX do momento do envio
x-pixnerva-signatureAssinatura HMAC-SHA256 do payload
Content-Typeapplication/json

Eventos disponíveis

EventoDescrição
sale.pendingCobrança criada, aguardando pagamento
sale.paidPagamento confirmado
sale.failedPagamento falhou
sale.expiredCobrança expirou sem pagamento
sale.refundedPagamento estornado (total ou parcial)
sale.status_changedQualquer mudança de status (inclui previousStatus no payload)
sale.med_createdContestação MED aberta (detalhes em data.med: status, motivo, valor, e2e, datas). Só para webhooks que assinam o evento.
sale.med_updatedMED atualizado — a fase atual vem em data.med.status (ex: defesa enviada / em análise). Só para webhooks que assinam o evento.
sale.med_acceptedContestação aceita — devolução ao pagador (data.med presente quando a adquirente informa a infração)
sale.med_rejectedContestação rejeitada (favorável ao seller). Só para webhooks que assinam o evento.
sale.med_cancelledContestação cancelada. Só para webhooks que assinam o evento.
withdrawal.completedSaque concluído — PIX enviado
withdrawal.failedSaque falhou no provedor — saldo devolvido
withdrawal.rejectedSaque rejeitado pelo admin — saldo devolvido
cashout.completedCashout via API (POST /cashout) concluído — entregue na URL de Webhook de Cashout
cashout.failedCashout via API falhou (saldo devolvido) — entregue na URL de Webhook de Cashout

Webhook de Cashout (saques via API): os eventos cashout.completed e cashout.failed (do POST /cashout) são entregues numa URL própria — a URL de Webhook de Cashout, configurada na aba Webhooks do painel (com signing secret próprio). Não vão na URL padrão.

Os eventos withdrawal.* vão na URL padrão / assinaturas do painel. Alternativa ao webhook: acompanhe o status por GET /cashout/:id ou GET /withdrawals/my-withdrawals/:id (consulte sempre pelo id UUID retornado na criação, nunca pelo e2e; estados terminais: completed, failed, rejected).

Payload de exemplo (sale.paid)

POST para sua URL de webhook
{
  "event": "sale.paid",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "paid",
    "amount": 100.00,
    "fee": 8.98,
    "netAmount": 91.02,
    "payerName": "João Silva",
    "payerEmail": "joao@email.com",
    "payerDocument": "12345678901",
    "paymentMethod": "PIX",
    "transactionId": "gw_tx_abc123",
    "createdAt": "2026-03-23T10: 00: 00Z",
    "paidAt": "2026-03-23T10: 02: 30Z"
  }
}

Identificadores para reconciliação: id (nosso), transactionId (adquirente), externalId (o número do pedido que você enviou) e endToEndId (E2E do PIX). O webhook cashout.completed retorna os mesmos identificadores (id, transactionId, externalId, endToEndId) mais os dados do beneficiário (beneficiaryName, pixKey).

Pagador e recebedor (disponível mediante habilitação)

Em contas habilitadas, as transações pagas trazem os objetos payer (quem pagou) e receiver (quem recebeu), cada um com name, document, bankIspb, bankAccount, bankBranch e bankName. Os dois objetos vêm sempre com as seis chaves; campo que a instituição de pagamento não informou vem null. Em contas não habilitadas os campos payer e receiver não aparecem.

  • sale.paid e GET /sales/:id (status paid): payer = pagador, conforme informado pela instituição de pagamento (o documento pode vir mascarado, ex.: 123****8901); receiver = sua empresa.
  • cashout.completed, withdrawal.completed, GET /cashout/:id e GET /withdrawals/my-withdrawals/:id (status completed): payer = sua empresa; receiver = beneficiário, conforme informado pela instituição de pagamento.
  • POST /cashout: quando a instituição confirma o PIX na própria chamada, a resposta já vem com status: "completed", endToEndId, payer e receiver. O webhook cashout.completed é enviado mesmo assim. Caso contrário a resposta segue processing e as partes chegam no webhook cashout.completed, que exige a URL de webhook de saque configurada no painel.
  • customer.document passa a ser opcional no POST /sales. Algumas instituições de pagamento exigem o documento do pagador: sem ele, a cobrança pode ser recusada — envie customer.document sempre que tiver.
sale.paid em conta habilitada (trecho)
{
  "event": "sale.paid",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "paid",
    "endToEndId": "E12345678202603231002abcdefghijk",
    "payer": {
      "name": "Maria Santos",
      "document": "12345678909",
      "bankIspb": "12345678",
      "bankAccount": "00098765",
      "bankBranch": "0001",
      "bankName": "Banco do Pagador"
    },
    "receiver": {
      "name": "Sua Empresa LTDA",
      "document": "11222333000181",
      "bankIspb": null,
      "bankAccount": null,
      "bankBranch": null,
      "bankName": null
    }
  }
}

Verificação de assinatura (HMAC-SHA256)

Para garantir que o webhook foi enviado pela plataforma e não foi adulterado, verifique a assinatura HMAC-SHA256 usando o secret do webhook.

  1. Extraia o timestamp e a signature dos headers
  2. Monte a string: timestamp.body (timestamp + ponto + body raw)
  3. Gere o HMAC-SHA256 usando seu secret
  4. Compare o resultado com a signature recebida
  5. Rejeite se o timestamp for maior que 5 minutos (proteção contra replay attack)
Node.js — Verificação de assinatura
const crypto = require("crypto");

function verifyWebhook(req, secret) {
  const timestamp = req.headers["x-pixnerva-timestamp"];
  const signature = req.headers["x-pixnerva-signature"];
  const body = JSON.stringify(req.body);

  // Proteção contra replay attack (5 min)
  const age = Date.now() / 1000 - Number(timestamp);
  if (age > 300) return false;

  // Gerar HMAC e comparar
  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + body)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Retentativas: Se sua URL não responder com status 2xx, a plataforma reenvia automaticamente: 1 min → 5 min → 30 min (máximo 4 tentativas).

8. Códigos de Erro

A API retorna códigos HTTP padrão. Erros incluem um corpo JSON com message e statusCode.

StatusSignificadoQuando ocorre
200SucessoRequisição processada com sucesso
201CriadoRecurso criado com sucesso (venda, saque)
400Dados inválidosCampos obrigatórios ausentes ou formato incorreto
401API Key inválidaChave ausente, expirada ou revogada
403Sem permissãoEmpresa inativa ou sem acesso ao recurso
404Não encontradoRecurso não existe ou pertence a outro seller
429Rate limitMuitas requisições — aguarde e tente novamente
500Erro internoFalha no servidor — contate o suporte
Exemplo de resposta de erro
{
  "message": "Saldo insuficiente para realizar o saque",
  "statusCode": 400
}

9. Exemplos de Código

Exemplos prontos para copiar e colar na sua integração. Substitua sk_live_... pela sua API Key real.

Criar cobrança PIX

curl -X POST https://pixnerva.com.br/api/sales \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_live_sua_chave_aqui" \
  -d '{
    "amount": 100.00,
    "description": "Plano Premium",
    "customer": {
      "name": "João Silva",
      "document": "12345678901",
      "email": "joao@email.com",
      "phone": "11999999999"
    }
  }'

Consultar saldo

curl -X GET https://pixnerva.com.br/api/withdrawals/balance \
  -H "x-api-key: sk_live_sua_chave_aqui"

Verificar assinatura do webhook

const crypto = require("crypto");

// Middleware Express para verificar webhook
function verifyWebhook(req, res, next) {
  const secret = process.env.WEBHOOK_SECRET;
  const timestamp = req.headers["x-pixnerva-timestamp"];
  const signature = req.headers["x-pixnerva-signature"];
  const body = JSON.stringify(req.body);

  // Proteção contra replay (5 min)
  const age = Date.now() / 1000 - Number(timestamp);
  if (age > 300) {
    return res.status(401).json({ error: "Timestamp expirado" });
  }

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

  const valid = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );

  if (!valid) {
    return res.status(401).json({ error: "Assinatura inválida" });
  }

  next();
}

// Uso
app.post("/webhooks/payment", verifyWebhook, (req, res) => {
  const { event, data } = req.body;

  if (event === "sale.paid") {
    console.log("Pagamento confirmado:", data.id);
    // Liberar produto/serviço para o cliente
  }

  res.status(200).json({ received: true });
});