Webhooks: eventos, assinatura e novas tentativas
O Praeko avisa a sua URL quando um item sai, falha ou volta para a fila. Você confere a assinatura, responde 2xx e deduplica pelo id da entrega.
admin. A URL precisa ser https:// com host público; localhost, IP privado e nomes internos são recusados com unsafe_url (a conferência roda no cadastro, na edição e a cada entrega). Máximo de 10 por workspace.O caminho de uma entrega
Cadastrar
POST /v1/webhooks com url e, se quiser, events (sem events, o webhook recebe post.sent e post.failed). A resposta traz o segredo whsec_… uma única vez (e de novo em POST /v1/webhooks/:id/rotate-secret, que vale na hora). A lista mostra só a dica do segredo.
Eventos
| Evento | Quando |
|---|---|
post.sent |
O item foi publicado na rede. |
post.scheduled-fail |
Uma tentativa falhou e o item voltou para a fila. Traz attempts e nextAttemptAt. |
post.failed |
O item desistiu: 3 tentativas ou falha definitiva. |
ping.test |
Só quando você chama POST /v1/webhooks/:id/test. |
Não existe evento de comentário novo: os comentários são lidos ao vivo das redes. Posts criados fora do Praeko também não geram webhook.
{
"id": "evt_…",
"type": "post.sent",
"createdAt": "2026-11-07T14:02:11.482Z",
"data": {
"post": { "id": "01M6…", "title": "Meu vídeo" },
"item": { "id": "01M6…", "network": "youtube", "channel": "01KA…", "url": "https://youtu.be/…", "attempts": 1 }
}
}Cada entrega traz também os cabeçalhos Praeko-Event, Praeko-Delivery e Praeko-Signature. O campo error, quando existe, passa pela mesma limpeza dos logs (tokens viram ***).
Conferir a assinatura
Praeko-Signature: t=<unix>,v1=<hex> é o HMAC-SHA256 de "<t>.<corpo cru>" com o segredo whsec_…. Use o texto cru, antes do JSON.parse.
Ler o corpo cru
No Express, use express.text({ type: "*/*" }) na rota.
Recalcular e comparar
Compare com timingSafeEqual e rejeite se t estiver a mais de 300 s do seu relógio.
import { createHmac, timingSafeEqual } from "node:crypto";
// corpo: o texto CRU recebido (antes de JSON.parse); cabecalho: req.headers["praeko-signature"]
export function assinaturaValida(segredo, cabecalho, corpo, toleranciaSeg = 300) {
const p = Object.fromEntries(String(cabecalho).split(",").map((x) => x.split("=")));
if (!p.t || !p.v1 || Math.abs(Date.now() / 1000 - Number(p.t)) > toleranciaSeg) return false;
const esperado = createHmac("sha256", segredo).update(`${p.t}.${corpo}`).digest("hex");
return esperado.length === p.v1.length && timingSafeEqual(Buffer.from(esperado), Buffer.from(p.v1));
}
// Express: app.post("/praeko", express.text({ type: "*/*" }), (req, res) => {
// if (!assinaturaValida(process.env.PRAEKO_WEBHOOK_SECRET, req.get("praeko-signature"), req.body)) return res.sendStatus(401);
// const evento = JSON.parse(req.body); /* deduplique por req.get("praeko-delivery") */ res.sendStatus(204);
// });Novas tentativas
| Tentativa | Espera depois da falha |
|---|---|
| 2ª | 1 min |
| 3ª | 5 min |
| 4ª | 25 min |
| 5ª | cerca de 2 h |
| 6ª | 6 h. Falhou de novo: a entrega fica failed. |
O cabeçalho Praeko-Delivery é o mesmo em todas as tentativas: guarde e ignore repetidos. Para reenviar à mão, POST /v1/webhooks/:id/deliveries/:delivery/retry; GET /v1/webhooks/:id/deliveries lista as entregas.
disabledReason, e as entregas pendentes são descartadas. Reativar zera a contagem (o teste manual não conta). As entregas ficam 30 dias.Rotas: /v1/webhooks.