Desenvolvedores

Webhooks

Eventos, assinatura HMAC e regras de entrega do webhook próprio.

O webhook próprio envia um POST em JSON para o seu sistema quando algo acontece na loja. Para cadastrar, veja o guia Webhook próprio. Esta página é a referência técnica.

A requisição#

Cabeçalho Conteúdo
content-type application/json
user-agent PromisseStore-Webhook/1.0
x-webhook-signature HMAC-SHA256 em hexadecimal do corpo bruto, com o Secret como chave
x-webhook-event Nome do evento
x-webhook-delivery ID da entrega, igual em todas as tentativas

Todo corpo traz o nome do evento em event_name.

Validar a assinatura#

Calcule o HMAC sobre os bytes exatos recebidos, antes de converter o JSON. Se você reformatar o corpo, a assinatura deixa de bater.

server.js
import crypto from 'node:crypto';
import http from 'node:http';

const SECRET = process.env.PROMISSE_WEBHOOK_SECRET;
if (!SECRET) throw new Error('Defina PROMISSE_WEBHOOK_SECRET');

function isValid(rawBody, signature) {
  const expected = Buffer.from(crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex'));
  const received = Buffer.from(String(signature ?? ''));
  return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}

function readBody(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    req.on('data', (chunk) => chunks.push(chunk));
    req.on('end', () => resolve(Buffer.concat(chunks)));
    req.on('error', reject);
  });
}

// Troque por um banco: a entrega é "pelo menos uma vez" e o processo pode reiniciar.
const processed = new Set();

http.createServer(async (req, res) => {
  if (req.method !== 'POST' || req.url !== '/webhooks/promisse') return res.writeHead(404).end();

  const rawBody = await readBody(req);
  if (!isValid(rawBody, req.headers['x-webhook-signature'])) return res.writeHead(401).end();

  const delivery = req.headers['x-webhook-delivery'];
  if (processed.has(delivery)) return res.writeHead(200).end();
  processed.add(delivery);

  const event = JSON.parse(rawBody);
  if (event.event_name === 'order_paid') {
    console.log(`Pedido #${event.order.number} pago: R$ ${event.order.total}`);
  }

  res.writeHead(200).end();
}).listen(3000);

Para testar, rode com PROMISSE_WEBHOOK_SECRET=seu_secret node server.js em um endereço público.

Eventos#

Evento Quando dispara Quem pode assinar
order_paid Pagamento confirmado ou pedido liberado da revisão Quem pode ver vendas
chat_created Chat aberto Quem tem acesso total
chat_message Mensagem do comprador ou da equipe Quem tem acesso total
feedback_create Avaliação recebida Quem pode ver avaliações

order_paid#

JSON
{
  "event_name": "order_paid",
  "order": {
    "id": "uuid", "number": 1001, "status": "paid", "total": 15.5, "currency": "BRL", "method": "pix", "gateway": "promissepay",
    "paid_at": "2026-10-31T15:04:05.000Z", "coupon": null,
    "customer": { "name": "Ana Lima", "email": "ana@exemplo.com", "phone": "11999998888" },
    "utm": {}
  },
  "items": [
    { "id": "uuid", "product_id": "uuid", "variant_id": null, "name": "Curso", "variant": null, "quantity": 1, "unit_price": 15.5 }
  ]
}

chat_created#

chat.type é pedido ou geral. Em chat geral, pedido vem null.

JSON
{
  "event_name": "chat_created",
  "desc": "Evento para chats criados",
  "chat": {
    "id": "uuid", "pedido": "uuid", "loja": "uuid", "name": "Ana Lima", "type": "pedido", "status": "pending",
    "buyer_mail": "ana@exemplo.com", "buyer_id": "uuid", "createdAt": "...", "updatedAt": "..."
  }
}

chat_message#

Dispara em mensagem visível do comprador ou da equipe. Mensagem de bot, boas-vindas automática e nota interna não disparam, para uma automação não reagir ao próprio bot.

message.role é buyer ou staff.

JSON
{
  "event_name": "chat_message",
  "store": { "id": "uuid", "name": "Minha Loja", "icon": null },
  "chat": { "id": "uuid", "name": "Ana Lima", "buyer_email": "ana@exemplo.com" },
  "message": {
    "id": "uuid", "content": "Oi, preciso de ajuda", "role": "buyer", "role_system": null, "author": "Ana Lima",
    "embeds": null, "attachments": [], "created_at": "..."
  }
}

feedback_create#

Dispara quando o comprador envia uma avaliação. Traz a avaliação, o item, o pedido, o produto e a variante, se houver. Valores em reais.

JSON
{
  "event_name": "feedback_create",
  "feedback": {
    "id": "uuid", "comment": "Ótimo", "tags": [], "is_anonymous": false, "is_approved": true, "star": 5, "like": 0,
    "product": "uuid", "variant": null, "cart": "uuid", "user": "uuid", "images": [], "createdAt": "..."
  },
  "carrinho": {
    "id": "uuid", "nome": "Curso", "imagem": null, "valor": 15.5, "quantidade": 1, "produto": "uuid", "variante": null,
    "categoria": "uuid", "pedido": "uuid"
  },
  "pedido": {
    "id": "uuid", "nome": "Curso", "quantidade": 1, "total": 15.5, "metodo": "pix", "status": "paid",
    "email": "ana@exemplo.com", "loja": "uuid"
  },
  "produto": { "id": "uuid", "nome": "Curso", "valor": 15.5, "slug": "curso", "status": true },
  "variante": null
}

O objeto produto traz também os demais dados do cadastro. variante vem preenchido quando a compra foi de uma variante.

webhook_test#

É o evento do botão de teste no painel. Tem uma tentativa só.

JSON
{
  "event_name": "webhook_test",
  "test": true,
  "message": "Evento de teste enviado pelo painel da Promisse Store",
  "store": { "id": "uuid", "name": "Minha Loja" },
  "sent_at": "..."
}

Regras de entrega#

Regra Como funciona
Sucesso Resposta 2xx.
Tempo limite 8 segundos. Responda rápido e processe depois.
Redirecionamento Não é seguido e conta como falha.
Novas tentativas Até 8, com espera crescente.
Fim das tentativas Resposta 410 encerra na hora.
Desativação Depois de 20 entregas seguidas com falha, o webhook é desativado.
Histórico Fica por 30 dias no painel.

Requisitos da URL#

  • Em produção, precisa ser https.
  • Só as portas 80 e 443.
  • Não pode ter usuário e senha na URL.
  • Não pode apontar para um endereço interno. A regra é conferida no cadastro e de novo a cada envio.

Trocar o Secret#

Use a opção de trocar o segredo no webhook. O Secret novo aparece uma única vez e o antigo para de valer, então atualize o seu sistema logo em seguida.