Webhooks vs polling for social media post status
After you publish, how do you know it actually went live? Compare webhook-based status updates vs polling approaches, with pros/cons and platform support for each.
Publishing is not the last step
You call the API. The response comes back. The status says “ok.” You move on.
Except “ok” often means “we received your request,” not “your post is live.” Instagram needs to process a media container before the post exists. LinkedIn may accept the request and delay publication. TikTok processes video asynchronously and can reject it minutes later. YouTube uploads take time, and a successful upload does not mean the video is public.
The publish call starts a process. Knowing when that process finishes — and whether it finished successfully — is a separate problem. And it is one that most publishing systems handle badly, if they handle it at all.
There are two approaches: polling and webhooks. Each has real tradeoffs that matter when you are building automation that needs to be reliable.
Polling: ask until you get an answer
Polling is the straightforward approach. After publishing, you periodically check the post status until it reaches a terminal state — published, failed, or rejected.
Publish ──▶ Wait ──▶ Check status ──▶ Still processing? │ │ │ ▼ │ Wait again ▼ Terminal state (published / failed)With Postproxy, this means calling the post status endpoint after publishing:
curl "https://api.postproxy.dev/api/posts/POST_ID" \ -H "Authorization: Bearer YOUR_API_KEY"The response includes per-platform status:
{ "id": "post_8xk2m", "status": "processing", "platforms": [ { "platform": "twitter", "status": "published", "published_at": "2026-02-19T14:32:01Z" }, { "platform": "linkedin", "status": "published", "published_at": "2026-02-19T14:32:03Z" }, { "platform": "threads", "status": "processing" } ]}X and LinkedIn are done. Threads is still processing its media container. You check again in a few seconds:
{ "id": "post_8xk2m", "status": "processed", "platforms": [ { "platform": "twitter", "status": "published", "published_at": "2026-02-19T14:32:01Z" }, { "platform": "linkedin", "status": "published", "published_at": "2026-02-19T14:32:03Z" }, { "platform": "threads", "status": "published", "published_at": "2026-02-19T14:32:18Z" } ]}All three are live. Your system can move on.
A polling implementation
async function waitForPublished(postId, maxAttempts = 20, intervalMs = 3000) { for (let attempt = 0; attempt < maxAttempts; attempt++) { const response = await fetch( `https://api.postproxy.dev/api/posts/${postId}`, { headers: { "Authorization": `Bearer ${POSTPROXY_API_KEY}` }, } );
const post = await response.json();
const allTerminal = post.platforms.every( (p) => p.status === "published" || p.status === "failed" );
if (allTerminal) return post;
await new Promise((resolve) => setTimeout(resolve, intervalMs)); }
throw new Error(`Post ${postId} did not reach terminal state after ${maxAttempts} attempts`);}
// Usageconst createResponse = await fetch("https://api.postproxy.dev/api/posts", { method: "POST", headers: { "Authorization": `Bearer ${POSTPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ post: { body: "Your post text here" }, profiles: ["twitter", "linkedin", "threads"], }),});
const created = await createResponse.json();const finalStatus = await waitForPublished(created.id);
console.log("Final per-platform results:", finalStatus.platforms);This works. It is simple to implement, simple to debug, and runs entirely from your side without any configuration on the receiving end.
Pros of polling
No infrastructure required. You do not need a publicly accessible endpoint. No webhook receiver, no tunnel, no firewall rules. The client drives the interaction.
Works everywhere. Polling works from serverless functions, cron jobs, CI pipelines, local scripts, n8n workflows — anywhere you can make an HTTP request. There are no connectivity requirements beyond outbound HTTPS.
Easy to debug. When something goes wrong, the debugging path is clear. Call the endpoint manually. See the response. The state is always queryable. You never wonder whether a webhook was sent and lost.
Deterministic timing. You control how often you check. You can start with frequent checks (every 2 seconds) and back off over time. The timing is explicit and visible in your code.
Resilient to failures. If your polling process crashes mid-loop, restart it and pick up where you left off. The state lives on the server. Nothing is lost because a client was not listening at the right moment.
Cons of polling
Wasted requests. Most polls return “still processing.” If a video takes 3 minutes to process on TikTok and you poll every 3 seconds, that is 60 requests for 59 “no change” responses and 1 meaningful one.
Delayed detection. Polling introduces a detection delay equal to half the poll interval on average. If you poll every 30 seconds, you learn about a status change an average of 15 seconds after it happened.
Scales poorly with volume. Ten posts polling every 3 seconds is 200 requests per minute. A hundred posts is 2,000 requests per minute. At scale, polling generates significant API traffic, most of which carries no new information.
Awkward timeout decisions. How long should you poll before giving up? Too short and you miss slow platforms. Too long and you waste resources on posts that will never resolve. Every timeout threshold is a guess.
Webhooks: get notified when something changes
Webhooks invert the model. Instead of asking the server “has anything changed?”, the server tells you when something changes.
Publish ──▶ Done (from your side)
... time passes ...
Server ──▶ POST to your endpoint ──▶ Status update arrivesWith Postproxy webhooks, you register an endpoint URL and select which events you want to receive. When a post’s status changes, Postproxy sends an HTTP POST to your endpoint with the event payload.
Configuring webhooks
Register a webhook endpoint in the Postproxy dashboard or via the API:
curl -X POST "https://api.postproxy.dev/api/webhooks" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Production status updates", "url": "https://yourdomain.com/webhooks/postproxy", "events": ["post.processed", "platform_post.published", "platform_post.failed"], "secret": "whsec_your_signing_secret", "active": true }'The name field is a label for your own reference — useful when you have multiple webhooks configured (one for production, one for staging, one that notifies Slack). The active field controls whether the webhook receives deliveries. Set it to false to pause a webhook without deleting it — handy during debugging or maintenance.
The events array controls what you receive:
| Event | Fires when |
|---|---|
post.processed | All platform-specific posts have been submitted for publishing |
platform_post.published | A single platform post transitions to published |
platform_post.failed | A single platform post transitions to failed |
platform_post.insights | Platform post insights (impressions, engagement) have been updated |
The post.processed event fires once all platform-specific posts have been submitted for publishing — meaning Postproxy has finished its work. Individual platforms may still be processing (video encoding, container creation), so subscribe to platform_post.published and platform_post.failed if you need to know the final outcome per platform — for example, posting a reply on X the moment the tweet goes live.
Webhook payloads
When an event fires, Postproxy sends a POST request to your endpoint:
{ "event": "post.processed", "timestamp": "2026-02-19T14:32:18Z", "data": { "id": "post_8xk2m", "status": "processed", "platforms": [ { "platform": "twitter", "status": "published", "published_at": "2026-02-19T14:32:01Z", "platform_post_id": "1892847561234" }, { "platform": "linkedin", "status": "published", "published_at": "2026-02-19T14:32:03Z", "platform_post_id": "urn:li:share:7029384756" }, { "platform": "threads", "status": "published", "published_at": "2026-02-19T14:32:18Z", "platform_post_id": "18042937261384" } ] }}The payload includes the full per-platform status, so your handler has everything it needs to take the next action — log the result, update a database, notify a Slack channel, or trigger a follow-up workflow.
Verifying webhook signatures
Every webhook request includes a signature header so you can verify it came from Postproxy and was not tampered with:
X-Postproxy-Signature: sha256=a1b2c3d4e5...Verify it using the signing secret you provided when creating the webhook:
import crypto from "crypto";
function verifyWebhookSignature(payload, signature, secret) { const expected = crypto .createHmac("sha256", secret) .update(payload) .digest("hex");
return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(`sha256=${expected}`) );}
// In your webhook handlerexport default async function handler(req, res) { const signature = req.headers["x-postproxy-signature"]; const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) { return res.status(401).json({ error: "Invalid signature" }); }
const { event, data } = req.body;
if (event === "post.processed") { const failed = data.platforms.filter((p) => p.status === "failed"); if (failed.length === 0) { console.log(`Post ${data.id} published to all platforms`); } else { console.log(`Post ${data.id} partially failed:`, failed); // Alert, retry, or escalate } }
res.status(200).json({ received: true });}Always return a 200 status promptly. If your handler returns a non-2xx status or times out, Postproxy will retry the delivery — which is useful for resilience, but means your handler should be idempotent to avoid processing the same event twice.
Pros of webhooks
Immediate notification. You learn about status changes the moment they happen, not on your next poll cycle. For workflows that need to react quickly — posting a reply, sending a notification, updating a UI — this latency matters.
Efficient at scale. One hundred posts in flight means one hundred webhook deliveries when they resolve — not thousands of polling requests. The server does the work of tracking state and only contacts you when something meaningful happens.
No wasted requests. Every webhook delivery carries new information. There are no “still processing” responses. Your system only processes events that represent actual state changes.
Event-driven architecture. Webhooks fit naturally into event-driven systems. A webhook fires, a handler processes it, downstream actions trigger. No polling loops, no sleep timers, no timeout logic.
Cons of webhooks
Requires a public endpoint. Your webhook receiver must be accessible over the internet. For serverless functions and deployed applications, this is usually not an issue. For local development, cron scripts, or CI pipelines, it means setting up a tunnel or deploying a receiver somewhere.
Delivery is not guaranteed. Network failures, server downtime, or misconfiguration can cause webhook deliveries to be lost. Retry policies help (Postproxy retries failed deliveries with exponential backoff), but a permanently unreachable endpoint will miss events.
Ordering is not guaranteed. If two platform statuses change in quick succession, the webhooks may arrive in a different order than the events occurred. Your handler should be resilient to out-of-order delivery — process each event based on its content, not its arrival sequence.
Harder to debug. When a webhook does not arrive, the debugging path is less clear. Was it sent? Was it received but rejected? Did the handler crash? You need logging on both sides — in Postproxy’s webhook delivery logs and in your handler.
Requires idempotent handlers. Because webhooks are retried on failure, your handler may receive the same event more than once. If your handler updates a database or triggers an action, it must handle duplicates gracefully. This is solvable, but it is engineering work that polling does not require.
When to use polling
Polling is the right choice when:
- You do not have a public endpoint. Local scripts, cron jobs, CI pipelines, or development environments where exposing a URL is impractical.
- You need simplicity. Polling is easier to implement, easier to debug, and has fewer moving parts. For a script that publishes one post and waits for it, polling is the obvious choice.
- You need the status synchronously. If your workflow cannot proceed until the post is confirmed live, polling lets you block and wait. Webhooks are asynchronous by nature — your workflow has to be designed to continue later when the event arrives.
- Volume is low. If you publish a handful of posts per day, the overhead of polling is negligible. The efficiency argument for webhooks only matters at scale.
When to use webhooks
Webhooks are the right choice when:
- You publish at volume. Dozens or hundreds of posts per day make polling expensive. Webhooks notify you only when something changes, keeping API traffic proportional to actual events, not to time.
- You need real-time reactions. Posting a reply on X the moment a tweet goes live. Sending a Slack message when a post fails. Updating a dashboard in real time. Anything where latency between the event and your response matters.
- Your system is event-driven. If your architecture is already built around events — message queues, serverless functions, workflow engines — webhooks slot in naturally. Polling requires a dedicated loop that does not fit the pattern.
- You want to decouple publishing from status tracking. With webhooks, the publishing step fires and forgets. A separate handler deals with outcomes. This separation makes both sides simpler.
The hybrid approach
In practice, the most robust systems use both.
Webhooks handle the happy path. When everything works, the webhook arrives promptly, your handler processes it, and the workflow continues. No polling, no wasted requests, no delay.
Polling handles the unhappy path. If a webhook does not arrive within an expected window, a fallback poller checks the status. This catches dropped webhooks, handler crashes, and edge cases where the webhook system itself has an issue.
Publish ──▶ Register expected webhook │ ├──▶ Webhook arrives? ──▶ Process, done. │ └──▶ No webhook after N minutes? │ ▼ Poll for status ──▶ Process, done.Implementation:
// After publishing, store the expected completionasync function onPostCreated(postId) { await db.insert("pending_posts", { post_id: postId, created_at: Date.now(), resolved: false, });}
// Webhook handler — the fast pathasync function onWebhookReceived(event) { if (event.event === "post.processed") { await db.update("pending_posts", { post_id: event.data.id, resolved: true, result: event.data, });
await handlePostResult(event.data); }}
// Fallback poller — runs every 5 minutes, catches anything missedasync function pollUnresolved() { const stale = await db.query( "SELECT * FROM pending_posts WHERE resolved = false AND created_at < ?", [Date.now() - 5 * 60 * 1000] );
for (const pending of stale) { const response = await fetch( `https://api.postproxy.dev/api/posts/${pending.post_id}`, { headers: { "Authorization": `Bearer ${POSTPROXY_API_KEY}` }, } );
const post = await response.json(); const allTerminal = post.platforms.every( (p) => p.status === "published" || p.status === "failed" );
if (allTerminal) { await db.update("pending_posts", { post_id: pending.post_id, resolved: true, result: post, });
await handlePostResult(post); } }}The webhook handles 99% of cases immediately. The poller catches the 1% that slip through. Together, they provide both the speed of webhooks and the reliability of polling.
How platforms handle this natively
Understanding how the underlying social platforms report status helps explain why a unified layer matters.
X (Twitter) is the simplest case. The tweet creation endpoint is synchronous — a successful response means the tweet is live. Media uploads are a separate multi-step process, but once the tweet is created, it exists. No polling or webhooks needed for status.
Instagram and Threads use Meta’s container model. You create a container, then must poll GET /{container-id}?fields=status_code until it returns FINISHED. Only then can you publish. Threads adds a mandatory wait (at least 30 seconds) between creation and publishing. There are no webhooks for container processing status.
LinkedIn accepts the post creation request and returns a URN, but video posts involve asynchronous processing. You poll the video asset status until processing completes. No webhook mechanism exists.
TikTok has a publish/status/fetch endpoint for checking whether a video post has been processed. Uploading and publishing are asynchronous. No webhooks.
YouTube uses Google’s resumable upload protocol. Video processing happens after upload. You can poll the video resource to check processing status. YouTube does have push notifications via PubSubHubbub for channel activity, but not for upload processing status.
Facebook is relatively synchronous for text and image posts. Video posts go through processing and can be polled via the video node. No webhooks for post processing status.
The pattern is clear: most platforms require polling for asynchronous operations (especially video), and none offer webhooks for post processing status. This means any multi-platform publishing layer — Postproxy included — has to manage this polling internally, and then surface the results to you through a cleaner interface.
What Postproxy does with this
Postproxy handles all per-platform polling internally. When you publish through the API, Postproxy manages the container creation and polling on Instagram, the processing waits on TikTok, the video status checks on LinkedIn and YouTube — all of it. Your system never needs to implement platform-specific polling loops.
What your system needs is a way to know when the publishing process is complete. That is where the choice between webhooks and polling — at the Postproxy layer — matters.
Both are available:
- Poll
GET /api/posts/{id}for on-demand status checks - Register webhooks at
POST /api/webhooksfor push-based notifications
Use whichever fits your architecture. Use both if reliability is critical.
The underlying complexity — seven platforms, each with different asynchronous behavior, different processing times, different status reporting mechanisms — is abstracted into a single post status with per-platform outcomes. Whether you learn about those outcomes through a webhook or a poll, the information is the same: what published, what failed, and why.
Choosing your approach
For most teams starting out, polling is the right first step. It is simpler, requires no infrastructure, and works reliably for low to moderate volume.
As your publishing volume grows, or as your architecture shifts toward event-driven patterns, add webhooks. They reduce API traffic, eliminate detection delay, and make your system reactive instead of inquisitive.
For production systems where reliability matters — and for social media publishing, it usually does — use both. Let webhooks handle the fast path. Let polling catch anything that falls through. The combination gives you the best of both approaches with the weaknesses of neither.