Skip to content

Webhooks

Subscribe to a publication's progress instead of polling. Webhooks are brand-scoped: an endpoint registered under a brand receives events only for that specific brand.

To configure webhooks, first list your brands and select the brand ID:

sh
# List your brands and pick one (the id looks like brand_…)
curl -s "$API/brands" -H "Authorization: Bearer $SK"
export BRAND="brand_…"

Register an endpoint

Register a URL, pick the events you care about, and target the brand-scoped webhooks endpoint:

sh
curl -s -X POST "$API/brands/$BRAND/webhooks" \
  -H "Authorization: Bearer $SK" -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example/hooks/socialized",
    "events": ["publication.completed", "publication.target.failed"]
  }' | jq

The response includes a signing secret (whsec_…) returned once, like an API key. Store it; you'll need it to verify deliveries. List your endpoints with GET /brands/{brandId}/webhooks (without secrets) and remove one with DELETE /brands/{brandId}/webhooks/{id}. The url must be https.

Events

EventFires when
publication.target.publisheda single target goes live
publication.target.faileda single target is rejected by its platform
publication.target.deleteda live post is deleted on its platform via the API
publication.target.removed_remotelya live post is observed to have been deleted manually on the platform
publication.completeda publication reaches a terminal state (done / partial / failed)
connection.reauth_requireda platform needs a connection reconnected (reconnect to keep publishing)

Verifying a delivery

Every delivery carries two headers:

  • X-Socialized-Signature: sha256=<hex>, an HMAC-SHA256 of the raw request body, keyed by your endpoint secret.
  • X-Socialized-Delivery: a stable id for this delivery (the same across retries).

Recompute the signature over the bytes you received and compare. Reject anything that doesn't match, so a forged request can't be mistaken for ours.

ts
import { createHmac, timingSafeEqual } from 'node:crypto';

// header value is "sha256=<hex>", so prefix the digest to match.
const expected = `sha256=${createHmac('sha256', endpointSecret).update(rawBody).digest('hex')}`;
const ok =
  signature.length === expected.length &&
  timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) return new Response('bad signature', { status: 401 });

Compute the HMAC over the exact bytes of the body, before any JSON parsing: re-serializing changes the bytes and breaks the signature. Compare in constant time.

Delivery

Deliveries are retried with backoff until they're accepted, so your handler should be idempotent: the same event may arrive more than once with the same X-Socialized-Delivery id, which you can dedupe on. Respond 2xx quickly and do the work asynchronously; a slow handler is a failed delivery.

The API is the product; the dashboard is its best-known client.