Webhooks
Entrega por webhook: envelope idêntico ao polling, cabeçalhos X-MeuPedido-*, verificação da assinatura HMAC, retentativas e desativação.
Com o modo de entrega WEBHOOK ou BOTH, o MeuPedido faz um POST na sua URL a cada evento, em vez de esperar a sua consulta. O corpo é o mesmo envelope que o polling devolve, byte a byte: um único desserializador atende os dois modos.
Webhook é uma extensão MeuPedido. Não é o /v1/newEvent descrito no padrão Open Delivery; a diferença está em Compatibilidade.
- Envio do MeuPedido
- Retentativa: um POST novo na mesma URL em 5 s, 10 s e 20 s
- Entregue: qualquer 2xx em até 10 s
- WEBHOOK_FAILED: o evento continua no polling, nada se perde
- Obrigatório nos dois caminhos: confirmar o evento
Configuração
A URL é configurada pelo lojista (ou por quem tem acesso ao painel dele) em Integrações > Open Delivery, ao criar ou editar a credencial:
- Modo de entrega
WEBHOOK(só push) ouBOTH(push e polling). - URL obrigatoriamente HTTPS.
- Segredo do webhook, gerado pelo MeuPedido e mostrado uma única vez ao salvar a URL. Guarde-o no seu cofre de segredos. Salvar a URL de novo rotaciona o segredo, e o anterior deixa de valer.
Não há como recuperar um segredo perdido: rotacione e atualize o seu servidor.
A requisição
POST {sua URL}
Content-Type: application/json
X-MeuPedido-Signature: 5f1d6a9c2b3e4f7a8d9c0b1e2f3a4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e
X-MeuPedido-Timestamp: 1758290531
X-MeuPedido-Event-Id: e1b9c0d2-4a5b-4c6d-8e7f-9a0b1c2d3e4f
X-MeuPedido-Event-Type: CREATED{
"eventId": "e1b9c0d2-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"eventType": "CREATED",
"orderId": "3f8a1c2d-5b6e-4f7a-9c8d-1e2f3a4b5c6d",
"orderURL": "https://api.meupedido.io/open-delivery/v1/orders/3f8a1c2d-5b6e-4f7a-9c8d-1e2f3a4b5c6d",
"createdAt": "2026-09-19T14:02:11Z"
}| Cabeçalho | Conteúdo |
|---|---|
X-MeuPedido-Signature | HMAC-SHA256 em hexadecimal minúsculo, calculado com o segredo sobre a string {timestamp}.{body}. |
X-MeuPedido-Timestamp | Instante do envio, em segundos Unix. Entra no cálculo da assinatura. |
X-MeuPedido-Event-Id | O mesmo eventId do corpo, para roteamento e deduplicação sem desserializar. |
X-MeuPedido-Event-Type | O mesmo eventType do corpo. |
Um envio por evento. O corpo nunca traz o pedido: busque em orderURL, como no polling.
Responder
Responda qualquer 2xx em até 10 segundos. Isso é tudo que o MeuPedido espera; o corpo da resposta é ignorado.
Faça o mínimo dentro desses 10 segundos: verifique a assinatura, enfileire o evento e responda. Buscar o pedido, gravar no banco e imprimir ficam para depois da resposta. Um 2xx tardio conta como timeout.
Entregue não é confirmado
O 2xx diz ao MeuPedido que a requisição chegou. Ele não tira o evento do feed. É obrigatório chamar POST /v1/events/acknowledgment com o eventId depois de processar, mesmo recebendo por webhook. Sem isso o evento continua no polling e nunca é limpo.
Verificar a assinatura
Toda requisição deve ser verificada antes de qualquer processamento. A verificação tem duas partes:
- Tempo. Rejeite se
X-MeuPedido-Timestampestiver a mais de 5 minutos do relógio do seu servidor, para frente ou para trás. Isso impede o reenvio de uma requisição capturada. - Assinatura. Calcule
HMAC-SHA256(secret, "{timestamp}.{body}"), converta para hexadecimal minúsculo e compare comX-MeuPedido-Signatureem tempo constante.
body é o corpo bruto da requisição, exatamente como chegou. Não desserialize e serialize de novo antes de calcular: qualquer mudança de espaço ou ordem de chaves invalida a assinatura.
As funções abaixo são puras: recebem o segredo, o timestamp, o corpo bruto e a assinatura, e devolvem true ou false. O parâmetro now é opcional e existe para testes; em produção, deixe o padrão.
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_SECONDS = 5 * 60;
export function verifySignature(secret, timestamp, body, signature, now = Math.floor(Date.now() / 1000)) {
const sent = Number(timestamp);
if (!Number.isInteger(sent) || Math.abs(now - sent) > TOLERANCE_SECONDS) return false;
const expected = createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex');
const received = String(signature ?? '').toLowerCase();
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(received, 'utf8');
return a.length === b.length && timingSafeEqual(a, b);
}Um endpoint completo
import express from 'express';
import { verifySignature } from './verify-signature.js';
const app = express();
// Corpo bruto: a assinatura é calculada sobre os bytes originais.
app.post('/webhooks/meupedido', express.raw({ type: 'application/json' }), async (req, res) => {
const body = req.body.toString('utf8');
const ok = verifySignature(
process.env.MEUPEDIDO_WEBHOOK_SECRET,
req.get('X-MeuPedido-Timestamp'),
body,
req.get('X-MeuPedido-Signature'),
);
if (!ok) return res.status(401).end();
await queue.push(JSON.parse(body)); // processa fora da requisição
res.status(204).end();
});Retentativas e falha
Uma tentativa falha quando a sua URL responde algo fora de 2xx, recusa a conexão ou não responde em 10 segundos. Nesse caso o MeuPedido tenta de novo com intervalos de 5, 10 e 20 segundos: são 4 tentativas em cerca de 35 segundos.
tentativa 1 t = 0 s
tentativa 2 t = 5 s
tentativa 3 t = 15 s
tentativa 4 t = 35 sEsgotadas as tentativas, o evento é marcado como WEBHOOK_FAILED e continua disponível no polling, de onde você o recupera na próxima consulta. Nada é perdido por causa de uma queda da sua URL.
O lojista vê cada tentativa, o status e o motivo em Integrações > Open Delivery, e pode reenviar um evento manualmente.
Desativação da URL
Após 20 falhas consecutivas, a URL é desativada e o MeuPedido para de enviar. Os eventos continuam sendo gerados normalmente e ficam disponíveis no polling. Quando o seu servidor voltar, o lojista reativa a entrega em Limpar fila, no painel.
Enquanto a URL estiver desativada, o polling é o único caminho. Uma integração resiliente em modo BOTH não percebe a desativação: o loop de polling continua trazendo tudo.
Recomendações
- Idempotência pelo
eventId. Retentativa e modoBOTHfazem o mesmo evento chegar mais de uma vez. Use oeventIdcomo chave única, exatamente como no polling. - Responda antes de processar. Enfileire e devolva
2xx; o processamento pesado fica para um worker. - Trate o webhook como um aviso, não como a fonte da verdade. Busque o pedido em
orderURL. Se um webhook falhar por completo, o polling entrega o mesmo evento. - Restrinja o segredo. Ele vive só no servidor que recebe o webhook. Nunca em cliente, repositório ou log.
- Rotacione ao suspeitar de vazamento. Salvar a URL de novo no painel gera um segredo novo; atualize o servidor no mesmo momento, porque o anterior deixa de valer imediatamente.
Próximos passos
- Polling: a confirmação obrigatória e o loop que recupera eventos com falha de webhook.
- Catálogo de eventos: todos os
eventTypeque a sua URL pode receber. - Obter credenciais: onde a URL e o modo de entrega são configurados.