What you can subscribe to
Rather than polling the publish history, register an endpoint and Dravo will POST events to it. Available events:
| Event | Fires when |
|---|---|
post.published | A job delivered to at least one account. |
post.failed | A job failed for every account. |
post.queued | A job entered the queue (optional notification). |
connection.revoked | A connected account was revoked or needs reconnect. |
Register endpoints in the dashboard or with POST /v1/webhooks. The signing secret is returned once at creation.
The payload
Dravo sends a JSON body describing the event. Every delivery has the same
envelope — id, type, created_at and an event-specific data object:
{ "id": "evt_b1d4f0c8", "type": "post.published", "created_at": "2026-06-23T10:15:30.482Z", "data": { "id": "9f2c1d7a-...", "account_ids": ["acc_8f2c1d", "acc_3b71e0"], "platforms": ["x", "linkedin"], "text": "Shipping webhooks today 🚀" }}post.failed and partial post.published deliveries also include an
error_message string in data. The id at the top level is the delivery's
event id — it also arrives in the X-Dravo-Delivery header, so you can use it to
deduplicate retries on your side.
Verifying the signature
Every delivery is signed so you can confirm it came from Dravo and was not tampered with. Two headers carry the proof:
| Header | Value |
|---|---|
X-Dravo-Signature | sha256=<hex>, the HMAC-SHA256 described below. |
X-Dravo-Timestamp | Unix seconds when the delivery was signed. |
X-Dravo-Event | The event type (e.g. post.published). |
X-Dravo-Delivery | Unique id for this delivery attempt. |
The signature is an HMAC-SHA256, keyed with your endpoint signing secret, over
`${timestamp}.${rawBody}` — the timestamp, a literal dot, then the exact
raw request body. The timestamp is inside the signed string, so a replayed
delivery with a stale timestamp won't verify.
X-Dravo-Signature may carry more than one comma-separated signature
(sha256=<a>,sha256=<b>) — this happens for 24h after you
rotate the secret, while both the new and the
previous secret are valid. Treat the delivery as authentic if any value
matches, so always split on commas:
import crypto from "node:crypto";
function verify(rawBody, headers, secret) {
const timestamp = headers["x-dravo-timestamp"];
const expected =
"sha256=" +
crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
// The header may hold multiple signatures during a secret rotation.
return headers["x-dravo-signature"]
.split(",")
.some((sig) =>
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
);
}Compute the HMAC over the raw request body, before any JSON parsing, or the
bytes will not match. Optionally reject deliveries whose X-Dravo-Timestamp is
more than a few minutes old to harden against replays.
Retries and auto-pause
If your endpoint doesn't return a 2xx, Dravo retries: three immediate attempts,
then several more over the next hours with exponential backoff. Every attempt is
recorded — list them with
GET /v1/webhooks/{webhook_id}/deliveries, and
re-send any one with replay.
After a long run of consecutive failures Dravo auto-pauses the endpoint so it
stops hammering a dead URL; it shows as auto_paused in the dashboard. Fix your
receiver and re-enable it (set active: true) to resume — that also clears the
failure streak.
Testing
Send a sample event to any endpoint with
POST /v1/webhooks/{webhook_id}/test. The response includes
the HTTP status your endpoint returned and the attempt count, so you can confirm
your receiver works before relying on it.