Webhooks
Receba os eventos da loja no seu sistema, verifique a assinatura e trate as novas tentativas.
Um webhook avisa o seu sistema quando algo acontece na loja: um pedido pago, um reembolso, um acesso revogado, uma entrega que falhou. A MinePag envia um POST com o evento em JSON para a URL que você cadastrar, assinado no padrão Standard Webhooks.
A lista de eventos e os dados de cada um estão em Eventos de webhook.
Cadastrar um endpoint
Só Proprietário e Administrador da loja gerenciam webhooks.
Prepare a URL
O endereço precisa ser HTTPS e público. Endereços locais (localhost, .local, .internal) e IPs privados ou reservados são recusados. Para testar na sua máquina, exponha a porta local com um túnel HTTPS.
Crie o endpoint
Abra Desenvolvedores › Webhooks e clique em Adicionar endpoint. Preencha:
- Tipo: Webhook de eventos, para integrar com seus sistemas, ou Serviço externo de entrega, para quem entrega produtos e confirma pela API (precisa receber
delivery.requested; veja Serviços externos). - Descrição (opcional): para reconhecer o endpoint no painel.
- URL: o endereço que vai receber os eventos. Cada URL só pode ser cadastrada uma vez por loja.
- Eventos: marque os eventos, organizados em Pedidos, Pagamentos, Acessos liberados, Entregas, Assinaturas e Clientes.
Clique em Criar endpoint.
Guarde o segredo
O painel mostra o segredo de assinatura, que começa com whsec_. Guarde em uma variável de ambiente do seu sistema, como MINEPAG_WEBHOOK_SECRET, e clique em Já guardei o segredo. Depois, o botão Mostrar segredo na página do endpoint exibe o valor de novo.
Teste
Na página do endpoint, use Enviar evento de teste. A MinePag envia um evento webhook.test assinado e mostra a resposta, como "O endpoint respondeu HTTP 200 em 85 ms.". O teste não entra no histórico nem é reenviado.
Gerenciar pela API
Os endpoints também podem ser gerenciados pela API, com uma chave secreta com os escopos webhooks:read e webhooks:write. Isso é útil para um app que se cadastra sozinho ao ser instalado na loja.
| Rota | O que faz |
|---|---|
GET /webhook-endpoints | Lista os endpoints, sem o segredo. |
POST /webhook-endpoints | Cria o endpoint. O segredo (secret) aparece só nesta resposta. |
GET /webhook-endpoints/{endpointId} | Consulta um endpoint. |
PATCH /webhook-endpoints/{endpointId} | Altera URL, descrição, tipo ou eventos. enabled: false pausa os envios e enabled: true reativa. |
DELETE /webhook-endpoints/{endpointId} | Remove o endpoint. |
POST /webhook-endpoints/{endpointId}/rotate-secret | Gera um segredo novo, devolvido só nesta resposta. O anterior assina junto por 24 horas. |
POST /webhook-endpoints/{endpointId}/test | Envia um webhook.test e devolve a resposta do endpoint. |
As alterações feitas pela API ficam nos logs de auditoria com a chave de API como autora. Veja os detalhes na referência da API.
O que a MinePag envia
POST /webhooks/minepag HTTP/1.1
Content-Type: application/json
User-Agent: MinePag-Webhooks/1.0 (+https://minepag.com.br)
webhook-id: 0193b7c3-1001-7abc-8def-100000000001
webhook-timestamp: 1791815473
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{"id":"0193b7c3-1001-7abc-8def-100000000001","type":"order.paid","apiVersion":"2026-10-01","createdAt":"2026-10-12T14:31:13.000Z","projectId":"…","data":{…}}| Cabeçalho | Conteúdo |
|---|---|
webhook-id | Identificador do evento. É igual ao id do corpo e se repete em todas as tentativas do mesmo evento. |
webhook-timestamp | Momento do envio, em segundos desde 1970 (Unix). Muda a cada tentativa. |
webhook-signature | Uma ou mais assinaturas v1,<base64> separadas por espaço. Durante a troca de segredo vêm duas. |
Resposta esperada
- Responda 2xx em até 10 segundos. Qualquer outro status, falha de conexão ou demora conta como falha.
- Redirecionamentos (3xx) não são seguidos e contam como falha.
- O corpo da sua resposta é ignorado. O primeiro 1 KB fica no histórico do endpoint para ajudar a investigar falhas.
Verificar a assinatura
Verifique toda requisição antes de confiar nela. A assinatura é um HMAC-SHA256 de {webhook-id}.{webhook-timestamp}.{corpo} com o segredo do endpoint (o trecho depois de whsec_, decodificado de base64), codificado em base64.
Use o corpo bruto
Calcule a assinatura sobre os bytes exatos recebidos, antes de qualquer JSON.parse. Reserializar o JSON muda espaços e a ordem das chaves, e a assinatura deixa de bater.
Os passos:
- Leia
webhook-id,webhook-timestampewebhook-signature. Se faltar algum, recuse. - Recuse se o horário estiver a mais de 5 minutos do relógio do seu servidor. Isso impede que alguém reenvie uma requisição antiga capturada.
- Calcule o HMAC esperado e compare, em tempo constante, com cada assinatura
v1do cabeçalho. Basta uma bater. - Só então leia o JSON e processe.
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';
const TOLERANCE_SECONDS = 5 * 60;
export function verifyMinePagWebhook(rawBody, headers, secret) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signatures = headers['webhook-signature'];
if (!id || !timestamp || !signatures) return false;
const now = Math.floor(Date.now() / 1000);
if (!/^\d+$/.test(timestamp) || Math.abs(now - Number(timestamp)) > TOLERANCE_SECONDS) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = createHmac('sha256', key).update(`${id}.${timestamp}.`).update(rawBody).digest();
return signatures.split(' ').some(entry => {
const [version, signature] = entry.split(',');
if (version !== 'v1' || !signature) return false;
const received = Buffer.from(signature, 'base64');
return received.length === expected.length && timingSafeEqual(received, expected);
});
}
const app = express();
// express.raw mantém o corpo como Buffer, sem reserializar o JSON
app.post('/webhooks/minepag', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifyMinePagWebhook(req.body, req.headers, process.env.MINEPAG_WEBHOOK_SECRET)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString('utf8'));
res.status(200).end();
queueEventProcessing(event);
});queueEventProcessing representa a sua fila ou rotina de processamento. Se preferir, as bibliotecas oficiais do padrão Standard Webhooks, disponíveis para várias linguagens, também verificam os envios da MinePag com o mesmo segredo whsec_.
Novas tentativas
Quando o envio falha, a MinePag tenta de novo com estes intervalos:
| Tentativa | Quando |
|---|---|
| 1ª | Na hora |
| 2ª | 1 minuto depois da 1ª |
| 3ª | 5 minutos depois da 2ª |
| 4ª | 30 minutos depois da 3ª |
| 5ª | 2 horas depois da 4ª |
| 6ª | 6 horas depois da 5ª |
| 7ª | 24 horas depois da 6ª |
São 7 tentativas no total; a última acontece cerca de 32 horas e meia depois da primeira. Todas usam o mesmo webhook-id. Se a 7ª também falhar, o envio fica como Tentativas esgotadas e só volta a ser enviado se alguém usar Reenviar evento no painel.
Situação do endpoint
| Situação | Quando |
|---|---|
| Ativo | Recebendo normalmente. |
| Com falhas | 3 falhas seguidas. Proprietários e administradores recebem uma notificação. Os envios continuam. Volta a Ativo no primeiro sucesso. |
| Desativado | 20 falhas seguidas ao longo de mais de 3 dias, ou desativado à mão. A MinePag para de enviar, avisa por notificação e registra nos logs de auditoria. |
Para voltar a receber, corrija o destino e use Reativar endpoint (ou PATCH /webhook-endpoints/{endpointId} com enabled: true). Ao desativar, as tentativas que estavam agendadas ficam como Tentativas esgotadas; depois de reativar, você pode reenviá-las pelo histórico. Eventos que aconteceram enquanto o endpoint estava desativado não são enviados nem aparecem no histórico: busque esses dados pela API (veja Conciliação diária).
Histórico e reenvio
A página do endpoint lista cada tentativa com o evento, a situação (Pendente, Entregue, Nova tentativa agendada ou Tentativas esgotadas), o status HTTP e o tempo de resposta. Abra uma tentativa para ver os dados enviados e o trecho da resposta do seu servidor.
Use Reenviar evento para mandar o mesmo evento de novo, com o mesmo webhook-id. O reenvio manual é uma tentativa avulsa: se falhar, não agenda outras. O reenvio fica nos logs de auditoria.
Trocar o segredo
Troque o segredo se ele vazar ou periodicamente:
- Na página do endpoint, clique em Gerar novo segredo e confirme. Pela API, use
POST /webhook-endpoints/{endpointId}/rotate-secret. - Copie o segredo novo. Por 24 horas, cada envio leva duas assinaturas no
webhook-signature: uma com o segredo novo e outra com o anterior. - Atualize o segredo no seu sistema dentro desse prazo. Os exemplos acima já aceitam qualquer uma das assinaturas, então a troca não perde eventos.
- Depois de 24 horas, só o segredo novo assina.
Boas práticas
- Responda rápido e processe depois. Valide a assinatura, coloque o evento numa fila e responda 2xx. Processamentos longos estouram os 10 segundos e geram reenvios.
- Deduplique pelo
webhook-id. O mesmo evento pode chegar mais de uma vez (nova tentativa, reenvio manual, sua resposta perdida no caminho). Guarde os ids processados e ignore repetidos. - Não dependa da ordem. Eventos podem chegar fora de ordem. Use
createdAtdo envelope ou consulte o recurso pela API antes de sobrescrever um estado. - Ignore tipos que você não conhece. Responda 2xx e siga em frente: novos eventos podem surgir.
- Confirme o que é crítico. Antes de liberar algo de alto valor, você pode consultar o pedido com
GET /orders/{orderId}. - Um endpoint por finalidade. Separe o que é ERP, o que é suporte e o que é serviço de entrega: cada um com seu segredo e seus eventos.
- Proteja o segredo. Guarde em variável de ambiente ou cofre de segredos, nunca no código.
Problemas comuns
| Sintoma | Causa provável |
|---|---|
| Assinatura nunca bate | Corpo reserializado (use o corpo bruto), segredo sem decodificar de base64, ou segredo de outro endpoint. |
| Assinatura bate no teste e falha depois | O relógio do servidor está atrasado ou adiantado mais de 5 minutos. Sincronize com NTP. |
| Envios com "O endpoint respondeu HTTP 301" ou 302 | A URL redireciona (ex.: http para https ou barra no fim). Cadastre a URL final. |
| Erro "Não foi possível conectar ao endpoint." | Firewall bloqueando, certificado inválido ou servidor fora do ar. |
| Eventos duplicados no seu sistema | Falta deduplicar pelo webhook-id. |