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
| Evento | Quando | dados |
|---|---|---|
cobranca.aprovada | Pagamento aprovado. | Cobrança |
cobranca.recusada | Cartão recusado, operador desistiu ou tipo não suportado. | Cobrança |
cobranca.cancelada | Cancelada pelo sistema ou na maquininha, ou expirou antes de a maquininha pegar. | Cobrança |
cobranca.erro | Resultado desconhecido: o cliente pode ter pago. Pode virar outro status depois. | Cobrança |
terminal.vinculado | O operador digitou o código na maquininha. | Terminal |
terminal.revogado | A 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çalho | Valor |
|---|---|
Content-Type | application/json; charset=utf-8 |
X-Conector-Evento | O evento, ex.: cobranca.aprovada. |
X-Conector-Assinatura | t=1792846328,v1=5f3a… |
User-Agent | ConectorPOS/1 |
{
"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
}
}
}{
"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):
v1 = hex( HMAC-SHA256( segredo cs_, "<t>.<corpo cru>" ) )- Leia o corpo cru, exatamente como chegou. Reformatar o JSON muda a assinatura.
- Calcule o HMAC de
t + "." + corpocom o segredo e compare comv1em tempo constante. - Recuse avisos com
tdistante 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):
| Tentativa | Depois de |
|---|---|
| 2ª | 1 minuto |
| 3ª | 5 minutos |
| 4ª | 15 minutos |
| 5ª | 60 minutos |
| Demais | De hora em hora, até completar 24 horas |
Repetidos e fora de ordem
O webhook é um aviso; a verdade é a consulta da cobrança. Ele pode:
- Chegar repetido (reenvio): guarde o
iddo aviso e ignore os já processados. - Chegar fora de ordem: aplique o
statusque vem emdados, sem supor a sequência. - Ter mais de um aviso para a mesma cobrança:
cobranca.erroe depoiscobranca.aprovada, quando a maquininha volta e confirma o pagamento. Sóerromuda depois; os outros status finais são definitivos.
Na dúvida, consulte
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.