Skip to content
Sign in Waitlist
Docs/Guides/Webhooks: events, signatures and retries

Webhooks: events, signatures and retries

Praeko calls your URL when an item goes out, fails or goes back to the queue. You verify the signature, answer 2xx and deduplicate by delivery id.

Before you startRegistering a webhook needs a key with the admin scope. The URL must be https:// with a public host; localhost, private IPs and internal names are rejected with unsafe_url (checked on create, on update and on every delivery). Up to 10 per workspace.

How a delivery works

1. EventThe item goes out or fails. Praeko records the delivery.
2. SendEvery minute, a POST to your URL with a 10 s timeout.
3. Your serverVerifies the signature and answers 2xx.
4. Result2xx = delivered. Any other status = retry.

Register

POST /v1/webhooks with url and, optionally, events (without events, the webhook gets post.sent and post.failed). The response contains the whsec_… secret only once (and again from POST /v1/webhooks/:id/rotate-secret, which takes effect immediately). The list shows only a hint of the secret.

Events

Event When
post.sent The item was published on the network.
post.scheduled-fail An attempt failed and the item went back to the queue. Includes attempts and nextAttemptAt.
post.failed The item gave up: 3 attempts or a permanent failure.
ping.test Only when you call POST /v1/webhooks/:id/test.

There is no new-comment event: comments are read live from the networks. Posts created outside Praeko do not trigger webhooks either.

post.sent body
{
  "id": "evt_…",
  "type": "post.sent",
  "createdAt": "2026-11-07T14:02:11.482Z",
  "data": {
    "post": { "id": "01M6…", "title": "My video" },
    "item": { "id": "01M6…", "network": "youtube", "channel": "01KA…", "url": "https://youtu.be/…", "attempts": 1 }
  }
}

Each delivery also carries the Praeko-Event, Praeko-Delivery and Praeko-Signature headers. The error field, when present, goes through the same scrubbing as the logs (tokens become ***).

Verify the signature

Praeko-Signature: t=<unix>,v1=<hex> is the HMAC-SHA256 of "<t>.<raw body>" with the whsec_… secret. Use the raw text, before JSON.parse.

1

Read the raw body

In Express, use express.text({ type: "*/*" }) on the route.

2

Recompute and compare

Compare with timingSafeEqual and reject if t is more than 300 s away from your clock.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

// body: the RAW text you received (before JSON.parse); header: req.headers["praeko-signature"]
export function assinaturaValida(secret, header, body, toleranceSec = 300) {
  const p = Object.fromEntries(String(header).split(",").map((x) => x.split("=")));
  if (!p.t || !p.v1 || Math.abs(Date.now() / 1000 - Number(p.t)) > toleranceSec) return false;
  const expected = createHmac("sha256", secret).update(`${p.t}.${body}`).digest("hex");
  return expected.length === p.v1.length && timingSafeEqual(Buffer.from(expected), 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 event = JSON.parse(req.body); /* deduplicate by req.get("praeko-delivery") */ res.sendStatus(204);
// });
Answer fastTaking longer than 10 s or answering with a redirect (3xx) counts as a failure. Do the heavy work after you answer 204.

Retries

Attempt Wait after the failure
2nd 1 min
3rd 5 min
4th 25 min
5th about 2 h
6th 6 h. If it fails again, the delivery becomes failed.

The Praeko-Delivery header is the same on every attempt: store it and ignore repeats. To resend by hand, use POST /v1/webhooks/:id/deliveries/:delivery/retry; GET /v1/webhooks/:id/deliveries lists the deliveries.

Automatic pauseAfter 15 consecutive failures the webhook pauses, with disabledReason, and pending deliveries are discarded. Reactivating resets the count (manual tests do not count). Deliveries are kept for 30 days.

Routes: /v1/webhooks.

Question or mistake on this page? contato@praeko.appView .md

Type to search pages, routes and error codes.