# SkedCast webhook events

Register an endpoint and subscribe to events. Every delivery is a POST with a JSON envelope `{ id, type, created, data }`.

- Signature: `X-SkedCast-Signature: t=<unix>,v1=<hex>[,v1=<hex>...]`, an HMAC-SHA256 over `<t>.<rawBody>`. During a secret rotation there is one `v1` entry per valid secret: accept a match on any entry.
- Deduplicate on the payload `id`. It equals the `X-SkedCast-Delivery` and `webhook-id` headers and is the same on every retry of one delivery.

## Events (44)

- `post.scheduled` — A new post was scheduled, or queued to publish now, to one or more destinations. data: postId, status, targetCount, platforms
- `post.published` — Every destination of the post that was not canceled published. At most once per post; each destination is also reported by target.published. data: postId, publishedCount, canceledCount, totalTargets, publishedAt
- `post.failed` — No destination of a post published and at least one failed (others may have been missed or canceled). At most once per post; each destination is also reported by target.failed or target.missed. data: postId, failedCount, missedCount, canceledCount, totalTargets, failedAt
- `post.partially_published` — The LAST destination of a post settled and the outcomes were mixed (at least one published, at least one failed or missed). At most once per post. data: postId, publishedCount, failedCount, missedCount, canceledCount, totalTargets, partiallyPublishedAt
- `post.missed` — No destination of a post published or failed, and at least one was missed: its time passed more than 6 hours while it waited (others may have been canceled). At most once per post; each destination is also reported by target.missed. data: postId, missedCount, canceledCount, totalTargets, missedAt
- `post.canceled` — Every destination of a post ended canceled, so nothing of it will publish. At most once per post. data: postId, canceledCount, totalTargets, reason, canceledAt
- `post.revised` — An already-scheduled post had its content edited in place. `reGated` is true when the edit returned an approved post to review, in which case nothing publishes until it is approved again. data: postId, changed, reGated, destinationsTouched, destinationsAdded, destinationsRemoved, revisedAt
- `post.recycled` — An evergreen post was recycled: a fresh copy was created and scheduled to publish again. `postId` is the new post; `sourcePostId` is the evergreen original. data: postId, sourcePostId, targetsCreated, scheduledAt
- `target.on_hold` — A destination is on hold. holdReason says why, and requiredAction says what to do (null when nothing is needed). Most holds, such as a posting limit, per-account pacing or media still processing, clear on their own. Three wait for you: plan_limit (change_plan), channel_needs_reconnect (reconnect_channel) and channel_paused (held by SkedCast for review: contact_support). data: postTargetId, platform, accountLabel, status, holdReason, requiredAction, retryAfterMs, quotaReason, heldAt
- `target.published` — A destination published successfully. data: postTargetId, platform, accountLabel, externalId, publishedAt
- `target.failed` — A destination failed and will not be retried automatically. requiredAction says what to do: retry (retry it, or edit it first) or check_platform. If the platform may already have received the post, failureReason is null, outcomeReason is delivery_unconfirmed and a retry is refused: check the account first. data: postTargetId, platform, accountLabel, reason, category, failureReason, outcomeReason, requiredAction, failedAt
- `target.missed` — A destination was not sent: its time passed more than 6 hours while it waited. outcomeReason says why. requiredAction is reschedule. data: postTargetId, postId, platform, accountLabel, outcomeReason, requiredAction, scheduledAt, missedAt
- `target.canceled` — A pending destination was canceled — by a member, a bulk action, or because its account was disconnected. data: postTargetId, postId, platform, reason, canceledAt
- `target.rescheduled` — A pending destination was moved to a new time. data: postTargetId, postId, platform, scheduledAt, rescheduledAt
- `media.ready` — An asset finished processing and is publishable. data: assetId, status
- `media.failed` — An asset could not be processed (unsupported, or it could not be converted) and will not be retried. data: assetId, status, reason
- `media.quarantined` — An asset was quarantined and will not be served. `quarantineReason` says why: `malware_detected`, `unsupported_type`, `scan_too_large`, `image_too_large`, `scanner_unavailable` or `unknown`. Only `malware_detected` means the file itself was flagged. data: assetId, status, reason, quarantineReason
- `media.deleted` — An asset was moved to the recycle bin; it is deleted permanently after your plan’s retention period. data: assetId, purgeDueAt
- `media.variant.ready` — A per-platform rendition of an asset is ready (at ingest, or from a publish-time fill). data: assetId, platform, status
- `media.variant.failed` — A per-platform rendition could not be produced (for example, the source exceeds that platform's duration cap). The asset may still publish elsewhere. data: assetId, platform, status
- `account.connected` — A channel was connected for the first time. data: accountId, platform, clientId, connectedVia, handle
- `account.reconnected` — An existing channel was re-authorized (its credential refreshed or reclaimed). data: accountId, platform, clientId, connectedVia, handle
- `account.disconnected` — A channel was disconnected — by a member, by disconnecting a whole login, by SkedCast support, or after a platform data-deletion request. data: accountId, platform, clientId, reason, disconnectedAt
- `account.token_expiring` — Credentials on one or more channels are about to expire — one event per workspace per check, covering all of them. data: count, accounts[{accountId, platform}]
- `account.reauth_required` — A channel's credential is no longer valid and publishing to it is paused until a person reconnects it. data: accountId, platform, clientId, source, reason
- `approval.requested` — A post was submitted for review and is waiting on a reviewer. data: postId, status
- `approval.decided` — A reviewer approved or rejected a post. data: postId, status, releasedTargets
- `approval.withdrawn` — A pending review request was withdrawn — the author un-requested it, or its channel was disconnected. data: approvalId, postId, reason, withdrawnAt
- `analytics.updated` — An account's analytics refresh finished and its metrics changed — read them now instead of polling. data: socialAccountId, platform, postsUpdated, daysUpdated, fetchedAt
- `report.completed` — A report run rendered and its export is ready to download. data: reportId, runId, status
- `report.failed` — A report run failed to render. data: reportId, runId, status, error
- `client.created` — A client group (a brand or person whose accounts are managed together) was created. data: clientId, name, type
- `client.updated` — A client's settings changed. data: clientId, changed
- `client.deleted` — A client was archived. data: clientId
- `campaign.created` — A campaign was created. data: campaignId, name, status, clientId
- `campaign.updated` — A campaign's fields changed. data: campaignId, changed
- `campaign.deleted` — A campaign was deleted; its posts survive un-grouped. data: campaignId
- `campaign.shifted` — A campaign's pending posts were rescheduled together by a fixed offset. data: campaignId, shiftMinutes, accepted, skipped
- `member.invited` — A person was invited to the workspace. data: inviteId, email, role, expiresAt
- `member.joined` — An invitee accepted and became a member. data: inviteId, memberId, role
- `member.removed` — A member was removed from the workspace. data: memberUserId
- `conversation.message.received` — A new comment, direct message, mention or review arrived in the inbox, once per message. data: conversationId, messageId, kind, platform, socialAccountId, clientId, receivedAt
- `conversation.updated` — A person or an API call changed a conversation’s status, assignee, labels or snooze. data: conversationId, status, assigneeUserId, labelIds, snoozedUntil, changed
- `link_in_bio.signup` — A visitor joined a link-in-bio page’s list: an email sign-up once they confirm it (verified), a phone sign-up when it is made (recorded consent, not a verified number). data: pageId, slug, signupId, kind, email, phone, name, verified, consentedAt, confirmedAt

## Machine-readable

- Everything in one file: https://skedcast.com/developers/llms.txt
- OpenAPI 3.1 contract: https://skedcast.com/openapi.json
- MCP server (remote, OAuth 2.1): https://api.skedcast.com/mcp
