Dravo.

Webhooks

View .md

Subscribe to publish events instead of polling, and verify each delivery with the signature.


What you can subscribe to

Rather than polling the publish history, register an endpoint and Dravo will POST events to it. Available events:

EventFires when
post.publishedA job delivered to at least one account.
post.failedA job failed for every account.
post.queuedA job entered the queue (optional notification).
connection.revokedA 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:

JSON
{  "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:

HeaderValue
X-Dravo-Signaturesha256=<hex>, the HMAC-SHA256 described below.
X-Dravo-TimestampUnix seconds when the delivery was signed.
X-Dravo-EventThe event type (e.g. post.published).
X-Dravo-DeliveryUnique 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:

JavaScript
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.