Webhooks: How to Know a Post Actually Went Live
Polling a status endpoint every few minutes to find out if a post went live is a waste of a request budget and a guarantee that you find out late. A webhook exists so your own systems hear about the publish the moment it happens instead of asking. Here is exactly how PostSider’s webhook works, verified against the actual delivery code, not the marketing description of it.
Subscribing happens in Settings, scoped per channel if you want it that way
A webhook lives under Settings, Webhooks in the dashboard, and it is organization-scoped: the URL you register only fires for posts published inside that organization. You can leave it unscoped so it fires for every connected channel, or narrow it to a specific set of integrations if you only care about, say, the LinkedIn and X publishes and not the other 28 networks in the workspace. There is also a send-test button on the same screen, which posts a real sample payload to your URL on demand, so you can build the receiving endpoint before a real post ever triggers it.
The payload is an array of post objects, and the event lives in a header
This is the part that catches people who assume a webhook body looks like {event, postId, status}. It does not. The body PostSider sends is an array, each entry shaped like a post record: id, content, publishDate, releaseURL, state, and a nested integration object carrying id, name, providerIdentifier, picture, and type. The event name is not inside that JSON at all. It arrives as the X-Postsider-Event header, currently always post.published, alongside an X-Webhook-Attempt header telling you which try this is.
Write your parser against the header for routing logic and against the array for content. Code that expects a single object with an event field buried inside it will silently break the first time it tries to read payload.event and gets undefined.
There is no failure event, because the webhook only fires on success
Worth stating plainly since it shapes how you should design around this: PostSider does not fire a webhook when a publish fails. The webhook call happens exclusively from the success path of the publish workflow. A failed publish sets the post’s own state to ERROR on the record, visible if you read post status through the API, but nothing gets pushed to your endpoint about it. If your integration needs to know about failures, poll status for posts you scheduled, or check them on your own cadence. Do not build a failure handler that waits on a webhook event that will never arrive.
Retries are 3 attempts with exponential backoff, and 4xx never retries
Delivery retries up to 3 times total, with a 1 second wait before the second attempt and a 2 second wait before the third. That retry only kicks in for a 5xx response or a network-level failure, the kind of thing that suggests your endpoint had a transient problem. A 4xx response is read as your side deliberately rejecting the payload, malformed auth, wrong shape, whatever it is, so PostSider logs it and does not try again. If your endpoint is flaky enough to 500 under normal load, you have up to two extra chances before the event is dropped. Design your receiver to return a fast 2xx once it has durably queued the payload, not after it has finished processing it, so a slow downstream step does not accidentally look like a failure and burn a retry.
Three things worth building on the webhook once it lands
Log to a warehouse. The simplest use: append every payload to wherever you already keep events, so “did this post actually go out” becomes a query instead of a guess six months from now.
Notify a Slack channel. A one-line message with the post’s integration.name and a link built from releaseURL turns a silent publish into something a team actually sees land, which matters more than it sounds like it should for anyone not staring at the dashboard all day.
Trigger follow-up content. A published post is sometimes the cue for something else: a cross-post reminder, a cue to draft a reply thread, a note to check performance in a day. The webhook is the trigger, not the logic, so keep the handler thin and push the actual work into whatever queue you already run.
SSRF protection and HMAC signatures: both are real now
Two things protect outbound traffic here, and they do different jobs. First, the transport layer: registering a webhook URL is validated against SSRF at both ends, HTTPS is required, and localhost, loopback, private, link-local, and CGNAT ranges are blocked on both IPv4 and IPv6, re-checked again at the moment of actual delivery so a DNS answer that changes after registration cannot sneak a request to an internal address. That protection exists to stop PostSider’s own servers from being tricked into hitting something they should not, which matters if you ever consider letting end users register their own webhook targets.
Second, the delivery layer: webhooks are HMAC-signed. Creating an endpoint generates a signing secret that is shown exactly once, so store it. Each delivery carries an X-Postsider-Signature header with a sha256= value, an X-Postsider-Timestamp in unix seconds, and an X-Webhook-Attempt telling you which try this is. The signature is an HMAC-SHA256 over timestamp + ”.” + rawBody, so verify against the raw request bytes, not a re-serialized JSON object, and reject any timestamp older than five minutes. The @postsider/node SDK ships Postsider.verifyWebhookSignature(signature, rawBody, secret, timestamp) that does this check for you.
Build the receiver assuming at-most-3 tries, not exactly-once
Every design decision above points at the same rule: verify the signature, build your receiver to be safely callable more than once, acknowledge fast, and do not wait on an event that this system does not send. Return a 2xx the moment you have durably stored the payload, keep any real processing out of the request path, and if you need failure visibility, poll for it instead of waiting for a push. I wrote about the broader shape of building against PostSider’s API, including the rate limits this webhook shares a budget with, in scheduling posts with a social media API. The same discipline applies here: read the actual behavior before you write the integration, not the behavior you assumed a webhook would have.
Frequently asked questions
What does a PostSider webhook fire on?
A successful publish. The event arrives with an X-Postsider-Event header set to post.published. There is no separate failure event today. A publish that errors out sets the post's own state to ERROR on the record, which you catch by reading post status through the API, not through a webhook.
How many times does PostSider retry a webhook delivery?
Up to 3 attempts, with exponential backoff of 1 second then 2 seconds between tries. Retries only happen on 5xx responses or network errors. A 4xx from your endpoint is treated as your side rejecting the payload on purpose, so it is logged and not retried.
Is the webhook payload signed so I can verify it came from PostSider?
Yes. When you create a webhook, PostSider generates a signing secret and shows it once. Every delivery then carries X-Postsider-Signature, X-Postsider-Timestamp, and X-Webhook-Attempt headers. The signature is an HMAC-SHA256 over timestamp + "." + rawBody, and the @postsider/node SDK exposes Postsider.verifyWebhookSignature(signature, rawBody, secret, timestamp) to check it. Reject timestamps older than five minutes.
What does the webhook payload actually contain?
An array of post objects, not a single flat event object. Each entry has id, content, publishDate, releaseURL, state, and an integration object with id, name, providerIdentifier, picture, and type. If your code expects a single-object envelope with an event name inside the body, check the header instead, the event type lives in X-Postsider-Event, not in the JSON.