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.
https://pixnerva.com.br/api1. 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.
# 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).
/salesParâmetros do Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| amount | number | SIM | Valor em reais (ex: 100.00). Mínimo R$ 0,01 / Máximo R$ 10.000,00 |
| customer | object | SIM | Dados do pagador (objeto aninhado — ver campos abaixo) |
| customer.document | string | SIM | CPF ou CNPJ do pagador (único campo obrigatório do customer) — obrigatório, exceto contas habilitadas para pagador/recebedor |
| customer.name | string | não | Nome completo do pagador |
| customer.email | string | não | Email do pagador |
| customer.phone | string | não | Telefone do pagador |
| description | string | não | Descrição da cobrança |
| items | array | não | Itens da cobrança [{description, quantity, unitPrice, tangible}] |
| expirationInSeconds | number | não | Expiração do PIX em segundos (300–86400, padrão: 86400) |
| postbackUrl | string | não | URL 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. |
| externalId | string | não | Referê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. |
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"
}{
"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.
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
| Campo | Descrição |
|---|---|
| utmSource | UTM Source (ex: facebook, google, tiktok) |
| utmMedium | UTM Medium (ex: cpc, social) |
| utmCampaign | UTM Campaign (nome da campanha) |
| utmContent | UTM Content (variação do criativo) |
| utmTerm | UTM Term (palavra-chave) |
| fbclid | Click ID do Facebook Ads (query param ?fbclid=...) |
| ttclid | Click ID do TikTok Ads |
| gclid | Click ID do Google Ads |
| fbp | Cookie _fbp do Facebook Pixel (browser id) |
| fbc | Cookie _fbc do Facebook Pixel (click id) |
| clientUserAgent | User-Agent do navegador do cliente final |
| clientIpAddress | IP do cliente final |
| eventId | Event ID para deduplicar pixel + CAPI (se vazio, derivamos do sale.id) |
{
"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.
/sales/:idGET /sales/a1b2c3d4-e5f6-7890-abcd-ef1234567890 x-api-key: sk_live_...
{
"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.
/sales?page=1&limit=20&status=paidParâmetros de Query
| Parâmetro | Tipo | Descrição |
|---|---|---|
| page | number | Página (padrão: 1) |
| limit | number | Itens por página (padrão: 20) |
| status | string | Filtrar: pending, paid, failed, refunded, expired |
| startDate | string | Data inicial (ISO 8601) |
| endDate | string | Data final (ISO 8601) |
| search | string | Busca por nome, email ou documento |
{
"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.
/productsParâmetros do Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| name | string | SIM | Nome do produto (até 140 caracteres). É o que aparece como “Objeto” na página de rastreio. |
| externalRef | string | não | Referê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. |
| tracking | object | não | Prazos 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). |
| description | string | não | Descrição exibida no checkout (até 1000 caracteres) |
| coverUrl | string | não | URL https da imagem de capa (até 500 caracteres) |
| memberArea | object | não | Conteúdo da área de membros ({ modules: [...] }). Sem ele o rastreio funciona normalmente e nada é liberado na entrega. |
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
}
}{
"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étodo | Rota | Descrição |
|---|---|---|
| GET | /products | Lista o catálogo da conta, com as ofertas de cada produto, no envelope { data, total, page, limit } |
| GET | /products/:id | Detalha um produto pelo ID da plataforma |
| PATCH | /products/:id | Atualiza os mesmos campos da criação — inclusive o externalRef |
| DELETE | /products/:id | Remove 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.
| Identificador | Nasce em | É usado de novo em |
|---|---|---|
| externalId | POST /sales — o número do pedido no seu sistema | POST /trackings, para localizar a venda |
| externalRef | POST /products — o seu SKU | POST /trackings, com o nome productRef |
| code | POST /trackings — o código da encomenda | O 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.
/trackingsParâmetros do Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| code | string | SIM | Código de rastreio exibido ao comprador (4-40 caracteres, letras/números/hífen — normalizado para maiúsculas) |
| saleId | string | não | ID da venda na plataforma. É obrigatório enviar saleId OU externalId. |
| externalId | string | não | Referência do pedido no SEU sistema — o mesmo externalId enviado ao criar a cobrança. Alternativa ao saleId. |
| productId | string | não | ID do produto na plataforma — define os tempos da timeline e o conteúdo da área de membros |
| productRef | string | não | Referência do produto no SEU sistema (SKU). Alternativa ao productId pra quem integra via API. |
| startedAt | string | não | Âncora da timeline em ISO 8601 — a etapa “Pedido confirmado” acontece neste momento (padrão: agora) |
POST /trackings
x-api-key: sk_live_...
Content-Type: application/json
{
"code": "BR123456789XX",
"externalId": "pedido-1042",
"productRef": "SKU-001"
}{
"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étodo | Rota | Descrição |
|---|---|---|
| GET | /trackings | Lista 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/:id | Detalha um rastreio, com currentStage e timeline computados |
| PATCH | /trackings/:id | Atualiza code, startedAt ou manualStage (override manual da etapa — só avança, nunca volta) |
| DELETE | /trackings/:id | Remove o rastreio |
5. Consultar Saldo
Retorna o saldo total, o valor retido (retenção de segurança) e o saldo disponível para saque.
/withdrawals/balanceGET /withdrawals/balance x-api-key: sk_live_...
{
"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.
/withdrawalsParâmetros do Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| amount | number | SIM | Valor do saque em reais |
| method | string | SIM | "pix" |
| pixKeyType | string | SIM | Tipo (lowercase): cpf, cnpj, email, phone, random |
| pixKey | string | SIM | Chave PIX para receber o saque |
| reference | string | não | ID de referência no seu sistema (devolvido nos webhooks) |
POST /withdrawals
x-api-key: sk_live_...
Content-Type: application/json
{
"amount": 500.00,
"method": "pix",
"pixKeyType": "cpf",
"pixKey": "12345678901",
"reference": "pedido-saque-789"
}{
"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.
/withdrawals/my-withdrawalsQuery Parameters
| Campo | Tipo | Descrição |
|---|---|---|
| page | number | Página (default: 1) |
| limit | number | Itens por página (máx: 100) |
| status | string | Filtrar: pending, approved, processing, completed, rejected, failed |
GET /withdrawals/my-withdrawals?page=1&limit=20&status=completed x-api-key: sk_live_...
{
"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.
/withdrawals/my-withdrawals/:idGET /withdrawals/my-withdrawals/a1b2c3d4-... x-api-key: sk_live_...
{
"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
| Header | Descrição |
|---|---|
| x-pixnerva-timestamp | Timestamp UNIX do momento do envio |
| x-pixnerva-signature | Assinatura HMAC-SHA256 do payload |
| Content-Type | application/json |
Eventos disponíveis
| Evento | Descrição |
|---|---|
| sale.pending | Cobrança criada, aguardando pagamento |
| sale.paid | Pagamento confirmado |
| sale.failed | Pagamento falhou |
| sale.expired | Cobrança expirou sem pagamento |
| sale.refunded | Pagamento estornado (total ou parcial) |
| sale.status_changed | Qualquer mudança de status (inclui previousStatus no payload) |
| sale.med_created | Contestação MED aberta (detalhes em data.med: status, motivo, valor, e2e, datas). Só para webhooks que assinam o evento. |
| sale.med_updated | MED atualizado — a fase atual vem em data.med.status (ex: defesa enviada / em análise). Só para webhooks que assinam o evento. |
| sale.med_accepted | Contestação aceita — devolução ao pagador (data.med presente quando a adquirente informa a infração) |
| sale.med_rejected | Contestação rejeitada (favorável ao seller). Só para webhooks que assinam o evento. |
| sale.med_cancelled | Contestação cancelada. Só para webhooks que assinam o evento. |
| withdrawal.completed | Saque concluído — PIX enviado |
| withdrawal.failed | Saque falhou no provedor — saldo devolvido |
| withdrawal.rejected | Saque rejeitado pelo admin — saldo devolvido |
| cashout.completed | Cashout via API (POST /cashout) concluído — entregue na URL de Webhook de Cashout |
| cashout.failed | Cashout 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)
{
"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.paideGET /sales/:id(statuspaid):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/:ideGET /withdrawals/my-withdrawals/:id(statuscompleted):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 comstatus: "completed",endToEndId,payerereceiver. O webhookcashout.completedé enviado mesmo assim. Caso contrário a resposta segueprocessinge as partes chegam no webhookcashout.completed, que exige a URL de webhook de saque configurada no painel.customer.documentpassa a ser opcional noPOST /sales. Algumas instituições de pagamento exigem o documento do pagador: sem ele, a cobrança pode ser recusada — enviecustomer.documentsempre que tiver.
{
"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.
- Extraia o
timestampe asignaturedos headers - Monte a string:
timestamp.body(timestamp + ponto + body raw) - Gere o HMAC-SHA256 usando seu
secret - Compare o resultado com a
signaturerecebida - Rejeite se o timestamp for maior que 5 minutos (proteção contra replay attack)
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.
| Status | Significado | Quando ocorre |
|---|---|---|
| 200 | Sucesso | Requisição processada com sucesso |
| 201 | Criado | Recurso criado com sucesso (venda, saque) |
| 400 | Dados inválidos | Campos obrigatórios ausentes ou formato incorreto |
| 401 | API Key inválida | Chave ausente, expirada ou revogada |
| 403 | Sem permissão | Empresa inativa ou sem acesso ao recurso |
| 404 | Não encontrado | Recurso não existe ou pertence a outro seller |
| 429 | Rate limit | Muitas requisições — aguarde e tente novamente |
| 500 | Erro interno | Falha no servidor — contate o suporte |
{
"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 });
});