Skip to content
SocialHelper
Get API key

Events

Receiving webhooks

Events, payloads and verifying signatures.

SocialHelper can call a URL of yours when a post finishes or an account needs attention, so you don’t have to poll. Add the URL under Webhooks in the dashboard; you’ll get a signing secret starting with whsec_.

The URL must be publicly reachable, and in production it must use HTTPS. Use Send test event to try it.

Events

post.published event
Every account in the post was published.
post.partially_published event
Some accounts were published and some failed.
post.failed event
No account was published.
post.cancelled event
Every account in the post was cancelled.
account.needs_reconnect event
An account lost access and must be reconnected in the dashboard.
ping event
The test event sent from the dashboard’s Webhooks page.

Post events are sent once, when the post first reaches its final status.

Payload

Each delivery is a POST with a JSON body. For post events, data is the post with its targets, exactly as the API returns it; for account events, it’s the account.

{
    "id": "evt_01ja8z…",
    "type": "post.published",
    "created_at": "2026-10-10T18:00:00Z",
    "data": {
        "id": "01JA8Z…",
        "object": "post",
        "status": "published",
        "targets": [
            "…"
        ]
    }
}
SocialHelper-Event header
The event type, same as type in the body.
SocialHelper-Delivery header
The event ID. It stays the same on retries, so use it to ignore duplicates.
SocialHelper-Signature header
t=<unix time>,v1=<signature>

Verify the signature

The signature is an HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, using your signing secret: v1 = HMAC_SHA256(secret, t + "." + body). Compare it in constant time, and reject old timestamps to stop replays (five minutes is a sensible window).

import crypto from "node:crypto";

// Use the raw request body: re-encoded JSON won't match the signature.
export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");

  const ok = expected.length === parts.v1?.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;

  return ok && fresh;
}

Delivery and retries

  • Reply with any 2xx status within 15 seconds. Anything else, or a timeout, counts as a failure.
  • Failed deliveries are retried up to 7 more times, after 10 seconds, 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour and 4 hours: about six hours in all.
  • Each attempt is signed again with a fresh timestamp.
  • The dashboard shows when the endpoint last succeeded or failed, and the last error.