Skip to content
UGC API

Webhooks

Webhooks tell your server when a post publishes, fails, or when an account disconnects. Set one endpoint per UGC API account, and verify each request with its signature.

Updated

Set your endpoint

bash
curl -X PUT https://ugcapi.dev/v1/webhook \
  -H "Authorization: Bearer ugc_live_..." \
  -H "Content-Type: application/json" \
  -d '{"url": "https://yourapp.com/webhooks/ugcapi"}'

The URL must be HTTPS. Your webhook secret is in the dashboard. Calling PUT /v1/webhook again replaces the URL.

Events

TypeSent when
post.publishedOne result of a post went live. Includes the public url.
post.failedOne result of a post failed. Includes error.code and error.message.
account.disconnectedAn account's access was revoked or its token stopped working.

Post events are sent per result. A video to TikTok and Instagram with a Story sends three events.

Payloads

json
{
  "type": "post.published",
  "created_at": "2026-11-01T17:00:42Z",
  "data": {
    "post_id": "post_abc",
    "account_id": "acc_ig_01",
    "platform": "instagram",
    "placement": "reel",
    "url": "https://www.instagram.com/reel/...",
    "external_id": "deliverable-981"
  }
}
json
{
  "type": "post.failed",
  "created_at": "2026-11-01T17:01:10Z",
  "data": {
    "post_id": "post_abc",
    "account_id": "acc_tt_01",
    "platform": "tiktok",
    "placement": "video",
    "external_id": "deliverable-981",
    "error": { "code": "platform_rejected", "message": "..." }
  }
}
json
{
  "type": "account.disconnected",
  "created_at": "2026-11-02T08:12:00Z",
  "data": { "account_id": "acc_tt_01", "platform": "tiktok", "external_id": "creator-204" }
}

Verify the signature

Every request has an X-UGCAPI-Signature header. It is the hex-encoded HMAC-SHA256 of the raw request body, keyed with your webhook secret. Compute it over the exact bytes you received, before parsing JSON, and compare in constant time.

javascript
import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(header || "");
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Delivery

  • Return any 2xx status quickly. Do slow work after responding.
  • Non-2xx responses and timeouts are retried, so the same event can arrive more than once. Make your handler idempotent, keyed on post_id, account_id and placement.
  • Events can arrive out of order. Use created_at when it matters.
  • You can always read the current state with GET /v1/posts/:id.

For handler design, see webhooks for social publishing. Error codes are in errors.

FAQ

Can I have more than one webhook URL?

No. There is one URL per UGC API account. Fan out to other systems from your handler.

Why does my signature check fail?

Usually because the body was parsed and re-serialized. Compute the HMAC over the raw bytes, with the exact secret from the dashboard.

Is there an event when a post is scheduled?

No. The create response is your confirmation. Events cover publishing, failures and disconnects.

Related