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
| Event | When | What you do |
|---|---|---|
post.published | A post went live on one account | Store the public URL. Show it to the user. |
post.failed | A post failed on one account | Show the reason. Offer a retry or a reconnect. |
account.disconnected | An account's token stopped working or was revoked | Pause 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.
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
- Respond fast. Return 2xx right away and do the work in a background job.
- Be idempotent. Retries can deliver the same event twice. Key your updates on the post ID and account ID, so a repeat changes nothing.
- Don't assume order. Handle a
post.failedthat arrives after you already marked something, by comparing timestamps. - Log the raw body for a few days. It makes support questions easy.
- Alert on spikes in
post.failedoraccount.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 code | Show the user |
|---|---|
platform_rejected | The platform's message, plus a link to edit and retry |
media_unreachable | Ask for a new file or re-upload |
account_not_found | The account was removed. Reconnect. |
insufficient_credits | Top up credits |
rate_limited | Wait a few minutes and retry |
The full list is in the errors reference.
Setting it up
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
- Expose your local server with a tunnel and register that URL with
PUT /v1/webhook. - Post to a test account with your free credits and watch the events arrive.
- Replay a stored body with a wrong signature and confirm you return 401.
- 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.