Pular para o conteúdo

Desenvolvedores

Webhooks

Avisamos o seu servidor quando uma cobrança termina ou uma maquininha é vinculada. Cada aviso é assinado com o seu segredo.

Configurar

Informe o endereço do webhook ao ativar o Conector POS (Portal do Cliente, Perfil › Conector POS) ou peça à GetPrime para o seu integrador. O endereço precisa ser HTTPS e público. Os avisos são assinados com o segredo cs_…, entregue junto com a chave de API.

O webhook é opcional: sem ele, consulte GET /cobrancas/{id} (veja Cobranças).

Eventos

EventoQuandodados
cobranca.aprovadaPagamento aprovado.Cobrança
cobranca.recusadaCartão recusado, operador desistiu ou tipo não suportado.Cobrança
cobranca.canceladaCancelada pelo sistema ou na maquininha, ou expirou antes de a maquininha pegar.Cobrança
cobranca.erroResultado desconhecido: o cliente pode ter pago. Pode virar outro status depois.Cobrança
terminal.vinculadoO operador digitou o código na maquininha.Terminal
terminal.revogadoA maquininha foi vinculada a outro terminal ou o terminal foi revogado.Terminal

Formato

POST com corpo JSON. Em dados vem o objeto completo, igual ao da consulta.

CabeçalhoValor
Content-Typeapplication/json; charset=utf-8
X-Conector-EventoO evento, ex.: cobranca.aprovada.
X-Conector-Assinaturat=1792846328,v1=5f3a…
User-AgentConectorPOS/1
cobranca.aprovada
{
  "evento": "cobranca.aprovada",
  "id": "9b2f6d0e-5c1a-4e7b-8f3d-2a1c0b9e8d7f",
  "criadoEm": "2026-10-26T14:32:08.512Z",
  "dados": {
    "id": "e4d3c2b1-0000-4f5e-8d7c-6b5a49382716",
    "terminalId": "3f6c1b2e-0000-4a5b-9c1d-7e8f90a1b2c3",
    "referencia": "loja7-pedido1234-t1",
    "valor": 2590,
    "tipo": "credito",
    "parcelas": 3,
    "parcelamento": "loja",
    "descricao": "Pedido 1234",
    "status": "aprovada",
    "cancelarSolicitado": false,
    "criadaEm": "2026-10-26T14:31:40.120Z",
    "expiraEm": "2026-10-26T14:34:40.120Z",
    "iniciadaEm": "2026-10-26T14:31:42.877Z",
    "concluidaEm": "2026-10-26T14:32:08.301Z",
    "resultado": {
      "adquirente": "pagbank",
      "cnpjCredenciadora": "08561701000101",
      "bandeira": "VISA",
      "autorizacao": "123456",
      "nsu": "000123",
      "nsuHost": "987654321",
      "serial": "PBF0000000000",
      "tipo": "credito",
      "parcelas": 3,
      "valorConfirmado": 2590,
      "codigoTransacao": "…",
      "idTransacao": "…",
      "pixTxId": null,
      "pixE2eId": null,
      "erroCodigo": null,
      "mensagem": null
    }
  }
}
terminal.vinculado
{
  "evento": "terminal.vinculado",
  "id": "1c0b9e8d-7f6a-4b5c-9d8e-7f6a5b4c3d2e",
  "criadoEm": "2026-10-26T13:05:11.044Z",
  "dados": {
    "id": "3f6c1b2e-0000-4a5b-9c1d-7e8f90a1b2c3",
    "nome": "Caixa 1",
    "referencia": "loja7-caixa1",
    "status": "ativo",
    "serial": "PBF0000000000",
    "modelo": "A930",
    "fabricante": "PAX",
    "adquirente": "pagbank",
    "versaoApp": "2.2.0",
    "vinculadoEm": "2026-10-26T13:05:10.982Z",
    "ultimoContatoEm": "2026-10-26T13:05:10.982Z",
    "criadoEm": "2026-10-26T13:01:47.300Z"
  }
}

Assinatura

O cabeçalho X-Conector-Assinatura traz o momento do envio (t, Unix em segundos) e a assinatura (v1):

Cálculo
v1 = hex( HMAC-SHA256( segredo cs_, "<t>.<corpo cru>" ) )
  1. Leia o corpo cru, exatamente como chegou. Reformatar o JSON muda a assinatura.
  2. Calcule o HMAC de t + "." + corpo com o segredo e compare com v1 em tempo constante.
  3. Recuse avisos com t distante do relógio do seu servidor (sugestão: mais de 5 minutos), para evitar repetição maliciosa.
import crypto from "node:crypto";
import express from "express";

const SEGREDO = process.env.CONECTOR_SEGREDO; // cs_...
const app = express();

// Use o corpo CRU: a assinatura é calculada sobre os bytes exatos que chegaram.
app.post("/conector/webhook", express.raw({ type: "application/json" }), async (req, res) => {
  const corpo = req.body.toString("utf8");
  if (!assinaturaValida(req.get("X-Conector-Assinatura"), corpo)) return res.sendStatus(401);

  const aviso = JSON.parse(corpo);
  if (await jaProcessado(aviso.id)) return res.sendStatus(200); // reenvio: ignore
  await enfileirar(aviso);                                      // processe fora da requisição
  res.sendStatus(200);                                          // responda em menos de 10 s
});

function assinaturaValida(cabecalho, corpo, toleranciaS = 300) {
  const partes = Object.fromEntries((cabecalho ?? "").split(",").map((p) => p.split("=", 2)));
  const t = Number(partes.t);
  if (!t || !partes.v1 || Math.abs(Date.now() / 1000 - t) > toleranciaS) return false;
  const esperado = crypto.createHmac("sha256", SEGREDO).update(`${t}.${corpo}`).digest("hex");
  return partes.v1.length === esperado.length &&
    crypto.timingSafeEqual(Buffer.from(partes.v1), Buffer.from(esperado));
}

Reenvio

Responda com qualquer status 2xx em até 10 segundos. Sem isso, reenviamos o mesmo aviso (mesmo id):

TentativaDepois de
2ª1 minuto
3ª5 minutos
4ª15 minutos
5ª60 minutos
DemaisDe hora em hora, até completar 24 horas
Processe o aviso numa fila e responda logo. Trabalho demorado dentro da requisição vira timeout e reenvio.

Repetidos e fora de ordem

O webhook é um aviso; a verdade é a consulta da cobrança. Ele pode:

  • Chegar repetido (reenvio): guarde o id do aviso e ignore os já processados.
  • Chegar fora de ordem: aplique o status que vem em dados, sem supor a sequência.
  • Ter mais de um aviso para a mesma cobrança: cobranca.erro e depois cobranca.aprovada, quando a maquininha volta e confirma o pagamento. Só erro muda depois; os outros status finais são definitivos.

Na dúvida, consulte

Antes de dar baixa numa venda de valor alto, ou quando um aviso parecer incoerente, confirme com GET /cobrancas/{id}.

Checklist

  • Endereço HTTPS público, com certificado válido.
  • Assinatura conferida sobre o corpo cru, em tempo constante, com tolerância de tempo.
  • Resposta 2xx em menos de 10 segundos; processamento em fila.
  • Avisos idempotentes pelo id.
  • cobranca.erro tratado como “conferir pagamento”, nunca como recusa.
  • Segredo cs_ guardado só no servidor.