Skip to content
UGC API

Webhooks for social publishing

Scheduled posts publish when your user isn't watching. Webhooks are how your app finds out what happened. This guide covers the events that matter, how to verify them, and how to build a handler that doesn't break.

Updated

Why polling isn't enough

You can call GET /v1/posts/:id to read results, but with hundreds of scheduled posts you'd be polling all day. Platforms also process media on their own time, so a post scheduled for 17:00 may be live at 17:01 or fail at 17:04. A webhook tells you the moment it happens.

The events

EventWhenWhat you do
post.publishedA post went live on one accountStore the public URL. Show it to the user.
post.failedA post failed on one accountShow the reason. Offer a retry or a reconnect.
account.disconnectedAn account's token stopped working or was revokedPause work for that account. Send a new connect link.

Events are per account. A post to 4 accounts can produce 3 post.published and 1 post.failed.

Verify the signature

Anyone can POST to your webhook URL. Check that the request came from UGC API. Each request has an X-UGCAPI-Signature header: the hex HMAC-SHA256 of the raw request body, keyed with your webhook secret. Compute the same value and compare in constant time. Use the raw body, not re-serialized JSON, or the bytes won't match.

javascript
import crypto from "node:crypto";
import express from "express";

const app = express();

app.post("/webhooks/ugcapi", express.raw({ type: "application/json" }), (req, res) => {
  const expected = crypto
    .createHmac("sha256", process.env.UGCAPI_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");
  const got = req.get("X-UGCAPI-Signature") || "";
  const ok = got.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const event = JSON.parse(req.body.toString("utf8"));
  queue.add(event); // do the work later
  res.status(200).end();
});

Rules for a solid handler

  1. Respond fast. Return 2xx right away and do the work in a background job.
  2. Be idempotent. Retries can deliver the same event twice. Key your updates on the post ID and account ID, so a repeat changes nothing.
  3. Don't assume order. Handle a post.failed that arrives after you already marked something, by comparing timestamps.
  4. Log the raw body for a few days. It makes support questions easy.
  5. Alert on spikes in post.failed or account.disconnected. They usually mean an expired token wave or a platform change.

Mapping events to your users

Set external_id on posts and connect links. Webhook events include it, so you can find the user or deliverable without a lookup table. See create a post.

Turning failures into actions

Error codeShow the user
platform_rejectedThe platform's message, plus a link to edit and retry
media_unreachableAsk for a new file or re-upload
account_not_foundThe account was removed. Reconnect.
insufficient_creditsTop up credits
rate_limitedWait a few minutes and retry

The full list is in the errors reference.

Setting it up

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"}'

Find your webhook secret in the dashboard. Payload formats are in the webhooks doc.

Testing your handler

  1. Expose your local server with a tunnel and register that URL with PUT /v1/webhook.
  2. Post to a test account with your free credits and watch the events arrive.
  3. Replay a stored body with a wrong signature and confirm you return 401.
  4. Send the same event twice and confirm nothing changes the second time.

Switch the URL back to production when you're done, since there is one webhook URL per API account.

If your endpoint is down, deliveries are retried, so a short outage doesn't lose events. For longer outages, reconcile by listing recent posts with GET /v1/posts?status=failed and status=published and comparing them with your records.

FAQ

What should my endpoint return?

Any 2xx status as soon as the signature checks out. Do slow work, like database writes to other systems or emails, in a background job after responding.

How do I verify a UGC API webhook?

Compute the hex HMAC-SHA256 of the raw body with your webhook secret and compare it to the X-UGCAPI-Signature header in constant time.

Can I get the same event twice?

Yes, if a delivery is retried. Make your handler idempotent by keying on post ID and account ID.

Do I get one event per post or per account?

Per account. A post to several accounts sends one event for each.

Can I still poll instead?

Yes. GET /v1/posts/:id returns per-account results at any time.

Related