# When Social Media Posts Fail: How PostEverywhere Retries and Recovers
> Scheduled posts fail for a handful of predictable reasons, and expired tokens are the biggest. Here is what PostEverywhere emits when a destination fails, how deliveries are retried, and the recovery path that does not need a human watching a dashboard.
**Source:** https://posteverywhere.ai/blog/when-social-posts-fail-webhooks
**Author:** Jamie Partridge
**Published:** 2026-09-12
---
A social media post failed and nobody was told. That is worse than one that never got scheduled: you planned it, you assumed it shipped, and you find out days later when someone asks why the campaign had a gap.
Scheduled posts fail for a small number of predictable reasons, and one of them dominates: **a social account's token expired and nobody noticed.** Everything else, rate limits, media that a platform rejected, a network that was briefly unavailable, is rarer and usually self-correcting.
This guide covers what PostEverywhere emits when something fails, how our own delivery retries work, and the recovery path that does not require a person watching a dashboard.
*Written by Jamie Partridge, Founder.*
## Table of Contents
1. [Why scheduled posts fail, in order of frequency](#why-scheduled-posts-fail-in-order-of-frequency)
2. [What PostEverywhere emits when a destination fails](#what-posteverywhere-emits-when-a-destination-fails)
3. [Partial failures, and why PostEverywhere treats them separately](#partial-failures-and-why-posteverywhere-treats-them-separately)
4. [How PostEverywhere retries a webhook delivery](#how-posteverywhere-retries-a-webhook-delivery)
5. [Catching token expiry before PostEverywhere has to fail a post](#catching-token-expiry-before-posteverywhere-has-to-fail-a-post)
6. [The PostEverywhere recovery loop, end to end](#the-posteverywhere-recovery-loop-end-to-end)
7. [FAQs](#faqs)
## Why scheduled posts fail, in order of frequency
**Expired or revoked tokens.** Every platform expires access tokens, and the windows differ wildly, as Meta's Graph API documentation and LinkedIn's marketing API docs both set out. A user changing their password, revoking an app, or losing admin rights on a Page all invalidate the connection silently. Nothing tells you until a publish attempt fails.
**Platform-side rejection of the media.** Aspect ratios, durations and file sizes vary by network and change without much notice, which the TikTok Content Posting API and YouTube Data API both document separately. A video that is fine on one platform can be rejected by another in the same [cross-post](/cross-posting).
**Rate limiting.** Less common than people expect on the [social media API](/social-media-api), though X's API is stricter than most. The [API allows 60 requests per minute per key](/blog/social-media-api-rate-limits), which conversational and scheduled use never approaches, though a bulk job that fans out aggressively can.
**Transient platform outages.** Networks have bad hours. These usually resolve on their own and are the best argument for automated retries rather than manual intervention.
The order matters because it tells you where to spend effort. Building elaborate handling for rate limits while ignoring token expiry optimises the rare case and leaves the common one unhandled.
> **Want failures to reach you instead of being discovered?** PostEverywhere pushes a signed event the moment a destination fails, on [every plan from $9/mo](/pricing). [Start a 7-day free trial](https://app.posteverywhere.ai/signup): card required, $0 charged today.
## What PostEverywhere emits when a destination fails
Three events cover the failure surface, and they are emitted today rather than reserved:
| Event | Meaning |
|---|---|
| `post.failed` | A destination failed after all platform-side retries |
| `post.partially_failed` | A post group ended with some destinations succeeded and some failed |
| `account.reconnect_needed` | An account's token was detected dead, so the user must reconnect |
`account.reconnect_needed` is the one worth wiring first. It fires when PostEverywhere detects a dead token, which means you can act **before** the next scheduled post fails rather than after. That converts a failed campaign into an email asking someone to reconnect.
The payload follows the standard envelope described in the [PostEverywhere webhooks guide](/blog/posteverywhere-webhooks), with `data` carrying the identifiers you need to reconcile: `post_id`, `destination_id`, `platform`, `account_id` and `account_name`.
Handle them distinctly rather than collapsing them into one branch:
```ts
switch (event.event) {
case 'account.reconnect_needed':
await emailOwner(event.data.account_name, event.data.platform);
await pauseQueueFor(event.data.account_id);
break;
case 'post.failed':
await flagForRetry(event.data.post_id, event.data.destination_id);
break;
case 'post.partially_failed':
await reconcileDestinations(event.data.post_id);
break;
}
```
## Partial failures, and why PostEverywhere treats them separately
A single post can fan out to many destinations. Publish one piece of content to four accounts and three can succeed while one fails, which is neither a success nor a failure in any useful sense.
Collapsing that into `post.failed` would be wrong in both directions: it implies nothing published when three things did, and a handler that retries the whole post would duplicate the three that worked.
`post.partially_failed` exists so you can reconcile at the destination level. Fetch `GET /v1/posts/:id`, look at which destinations carry which status, and retry only the ones that failed. That is also why `destination_id` appears in the payload alongside `post_id`: the destination is the unit of success, not the post.
This matters most for [bulk scheduling](/bulk-scheduling), where one API call can create many posts across many accounts and a naive retry multiplies rather than repairs.
## How PostEverywhere retries a webhook delivery
Two different retry systems are easy to confuse, so it is worth separating them.
**Platform publishing retries** happen inside PostEverywhere before `post.failed` is emitted at all. By the time you receive that event, we have already tried.
**Webhook delivery retries** are about reaching *your* endpoint. If your receiver returns anything other than a `2xx` within 10 seconds:
| Attempt | Delay before this attempt |
|---|---|
| 1 | immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
| 6 | 6 hours |
After **6 failed attempts** the delivery is marked `dead`. After **20 consecutive failures on the same webhook**, the webhook is **auto-disabled** and must be re-enabled with `PATCH /v1/webhooks/:id` and `{"is_active": true}`, which resets the counter.
The consequence people meet in production: **a broken endpoint eventually turns your webhook off**, and silence then looks identical to nothing happening. Alert on your own error rate rather than trusting absence of events. The ladder spans just under eight hours, which survives a deploy or a short outage but not a weekend.
## Catching token expiry before PostEverywhere has to fail a post
The best failure is the one that never reaches a post. Two mechanisms help.
**Subscribe to `account.reconnect_needed`.** This is the earliest warning available, fired on detection rather than on the next publish attempt.
**Check account health before publishing.** The [hosted MCP server](/mcp) exposes an account-health tool, and the REST equivalent reports token status, recent failures and whether an account can currently post. An agent that checks health before a batch will skip or warn rather than queue into a dead connection. This is the difference between an agent that works and one you have to babysit.
For teams running many client accounts, [multi-account management](/multi-account-management) covers the workspace side, where one expired token in twenty is normal rather than exceptional.
> **Prefer to see the tools first?** The [MCP server page](/mcp) lists all 38 tools by group, and the [developer docs](/developers) cover the REST equivalents including the full [webhooks reference](/docs/webhooks).
## The PostEverywhere recovery loop, end to end
Put together, the loop needs no human in the normal case:
1. **`account.reconnect_needed` arrives.** Pause that account's queue and email whoever owns the connection with a reconnect link.
2. **`post.failed` arrives for something already queued.** Record it against the destination, not the post.
3. **The owner reconnects.** Retry the failed destinations, either through the retry tools or by re-creating those destinations.
4. **`post.published` arrives.** Clear the flag and store the `platform_post_id` for later [analytics](/social-media-analytics).
Steps 1 and 3 are the ones that turn a silent gap into a self-healing pipeline. Everything else is bookkeeping.
If you are building this inside an [AI agent](/agents), [building an AI social media agent](/blog/build-an-ai-social-media-agent-with-posteverywhere) shows the same loop in code, and [why your AI agent should not poll PostEverywhere](/blog/ai-agents-webhooks-not-polling) explains why the events beat a status check. Verification comes first either way: [verify PostEverywhere webhook signatures](/blog/verify-posteverywhere-webhook-signatures) before you act on any of these payloads.
## FAQs
### Why did my scheduled social media post fail?
Most often an expired or revoked access token on the connected account. Password changes, app revocations and lost Page admin rights all invalidate connections silently. Media rejected by a platform, rate limiting and transient outages account for most of the remainder.
### What does PostEverywhere send when a post fails?
`post.failed` for a single destination that failed after retries, and `post.partially_failed` when a post group ends with some destinations succeeded and some failed. Both carry `post_id`, `destination_id`, `platform` and `account_id` so you can reconcile precisely.
### How do I know a social account needs reconnecting?
Subscribe to `account.reconnect_needed`. It fires when PostEverywhere detects a dead token, which is before the next scheduled post attempts to publish, so you can pause the queue and ask the owner to reconnect rather than discovering it through a failure.
### How many times does PostEverywhere retry a webhook delivery?
Six attempts: immediate, 30 seconds, 2 minutes, 10 minutes, 1 hour, then 6 hours. After six the delivery is dead. After 20 consecutive failed deliveries the webhook is auto-disabled and must be re-enabled with a `PATCH` request.
### What is the difference between post.failed and post.partially_failed?
`post.failed` is one destination failing. `post.partially_failed` is a post group with mixed outcomes across destinations. The distinction stops you retrying a whole post when three of four destinations already published, which would duplicate the successful ones.
### Can I retry a failed post automatically?
Yes. PostEverywhere exposes retry tools for individual and bulk failed posts, and they are reachable from the REST API and the MCP server. The usual pattern is to retry after the underlying cause is fixed, which for token expiry means after the account has been reconnected.
### Why has my webhook stopped receiving events entirely?
Check whether it was auto-disabled. Twenty consecutive failed deliveries switch a webhook off, and from then on silence looks the same as no activity. Re-enable it with `PATCH /v1/webhooks/:id` and `{"is_active": true}`.
---
Failures are not avoidable, but discovering them days later is. One subscription to `account.reconnect_needed` and `post.failed` turns the worst class of scheduling bug into an email nobody had to go looking for.
[Start your 7-day free trial](https://app.posteverywhere.ai/signup) and subscribe your first endpoint to the failure events before you need them.
The [PostEverywhere webhooks guide](/blog/posteverywhere-webhooks) has the full event list, and [API vs MCP](/blog/social-media-api-vs-mcp) covers whether to drive recovery from code or from an assistant.