OmniSocialsOmniSocials
DocumentationAPI reference
Help centerSupport
  • Documentation
  • API reference
Developers
  • Quickstart
  • API reference
  • SDKs
  • MCP server
  • CLI
For AI agents
  • Docs for AI agents
  • llms.txt
  • OpenAPI spec
  • Postman collection
Product
  • OmniSocials
  • Pricing
  • Changelog
  • Help center
Company
  • Support
  • Privacy policy
  • Terms of service

© 2026 OmniSocials

OmniSocials on GitHubOmniSocials on XOmniSocials on LinkedIn
Overview
Get started
SDKs
CLI
MCPDocs for AI agentsWebhooks
Guides
Platforms
Integrations
Migrate
Plans and billing
GlossaryChangelogAPI reference
Documentation

Webhooks for post events

Subscribe to events to get real-time notifications when posts are published, scheduled, or fail, and when a reviewer approves or rejects a post in an approval workflow. Webhooks are the right choice when you want to know about state changes without polling the API.

Event catalog

EventWhen it firesdata.statusMain payload fields
post.scheduledA post is created or updated into the scheduled state through the API (POST /posts/create or PATCH /posts/:id). Not sent for posts scheduled in the dashboard, or for a post that waits for approvalscheduledscheduled_at. targets is empty and published_at is null
post.publishedA publish run ends with at least one channel live. Sent again after every retry run, manual or automaticposted, or warning when some channels failedpublished_at, and targets[] with platform, status, native_post_id, and error on a failed channel
post.failedA publish run ends with no channel live. An automatic retry can still followfailedtargets[] with platform, status and error
post.approvedThe last step of a post's approval workflow approves itscheduled, or posting when the publish time has passedapproval with status, decided_by and reason (null). targets is empty
post.rejectedAn approver rejects the post. It will not publishrejectedapproval with status, decided_by and the reason. targets is empty

Every event uses the same envelope: id (the same value as the X-OmniSocials-Delivery header), type, created_at, and data with post_id, workspace_id, status, post_type (Post, Story or Reel), scheduled_at, published_at and targets. data.status is the stored status, so a published post reads posted, not published.

One post can send several events, for example post.failed and later post.published when a retry succeeds. Keep your state per data.post_id; see Retries and duplicate posts.

Creating a webhook

TerminalTerminal
curl -X POST https://api.omnisocials.com/v1/webhooks \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/omnisocials", "events": ["post.published", "post.failed"] }'

Response:

JSON
{ "data": { "id": "5f3b0c1e-9a2d-4e77-b1c4-8a2f6d9e0b12", "url": "https://your-app.com/webhooks/omnisocials", "events": ["post.published", "post.failed"], "is_active": true, "created_at": "2026-04-10T14:22:00Z", "secret": "a1b2c3d4e5f6...c7d8e9f0" }, "message": "Webhook created. Save the secret - it will only be shown once." }

The id is a UUID and the secret is a 64-character hex string returned exactly once on creation. Store the secret. You will need it to verify incoming webhook signatures.

Available events

Subscribe to any of the five events in the Event catalog. These are the only five valid events. Creating or updating a webhook with any other value (including a "*" wildcard) returns 400. There is no Social Inbox event (a new DM, comment, or mention) yet. Poll GET /inbox/conversations instead.

Partial publishes fire post.published, not a separate event. When a post succeeds on some platforms and fails on others, the payload's data.status is "warning" and the per-platform outcomes are in data.targets (each with its own status). Inspect targets to see exactly which platforms succeeded or failed.

Payload shape

JSON
{ "id": "8c1e0a2b-4d5f-4a67-9b0c-1d2e3f4a5b6c", "type": "post.published", "created_at": "2026-04-10T14:22:00Z", "data": { "post_id": "123", "workspace_id": 456, "status": "posted", "post_type": "Post", "scheduled_at": "2026-04-10T14:20:00Z", "published_at": "2026-04-10T14:22:00Z", "targets": [ { "platform": "instagram", "status": "success", "native_post_id": "18023456789012345" }, { "platform": "linkedin", "status": "failed", "native_post_id": null, "error": "The LinkedIn token has expired. Reconnect the account." } ] } }

data.targets holds one entry per selected platform. native_post_id is the platform's own post ID (present when that platform succeeded); error appears only on a non-successful target. data.status is the overall post status (posted, warning for a partial success, or failed). Always branch on the top-level type before accessing data.

post.scheduled, post.approved and post.rejected are delivered before anything is published, so their data.targets array is empty and published_at is null.

Approval events

post.approved and post.rejected use the same envelope and carry one more object, data.approval, that says how the review ended:

JSON
{ "id": "e7c9a1b2-3d4e-5f6a-7b8c-9d0e1f2a3b4c", "type": "post.rejected", "created_at": "2026-10-02T09:00:05.000Z", "data": { "post_id": "123456", "workspace_id": 789, "status": "rejected", "post_type": "Post", "scheduled_at": "2026-10-03T14:00:00.000Z", "published_at": null, "targets": [], "approval": { "status": "rejected", "decided_by": "c4a09e1d-7b52-4f38-a6d1-3e9f8b2c5d60", "reason": "Wrong product photo" } } }
FieldValue
data.approval.statusapproved or rejected
data.approval.decided_byUser id of the approver who gave the last approval, or who rejected the post
data.approval.reasonThe reason the approver gave with a rejection. null on post.approved and when the approver gave no reason

On post.approved, data.status is scheduled, or posting when the publish time has already passed. On post.rejected it is rejected. post.approved fires once, when the last step is approved; an approval of an earlier step sends no event.

The payload names the outcome only. For every step, every approver and the comment thread, call GET /posts/:id/approval with the post_id from the event.

Verifying signatures

Every outbound webhook call is signed with HMAC-SHA256 using your webhook's secret. Each delivery carries these headers:

HeaderValue
X-OmniSocials-Signaturet=<unix_timestamp>,v1=<hex_signature>
X-OmniSocials-TimestampThe same unix timestamp, on its own
X-OmniSocials-EventThe event type (e.g. post.published)
X-OmniSocials-Webhook-IdThe webhook's ID
X-OmniSocials-DeliveryA unique ID per event, stable across retry attempts. Use it to deduplicate.

The signature is computed over `${timestamp}.${rawBody}` (the timestamp, a literal dot, then the raw JSON body), the same scheme Stripe uses. To verify: parse t and v1 out of the header, recompute the HMAC over timestamp.rawBody, and compare against v1. A mismatch means the request is not from OmniSocials. Reject it.

JavaScript
import crypto from 'crypto' function verifyWebhook(rawBody, signatureHeader, secret) { // signatureHeader looks like "t=1744294920,v1=abc123..." const parts = Object.fromEntries( signatureHeader.split(',').map((kv) => kv.split('=')) ) const { t: timestamp, v1: signature } = parts if (!timestamp || !signature) return false const expected = crypto .createHmac('sha256', secret) .update(`${timestamp}.${rawBody}`, 'utf8') .digest('hex') // timingSafeEqual throws when lengths differ, so check first const sigBuf = Buffer.from(signature) const expBuf = Buffer.from(expected) if (sigBuf.length !== expBuf.length) return false return crypto.timingSafeEqual(sigBuf, expBuf) }

Verify against the raw request body, before any JSON parsing or reserialization. Always compare with a constant-time function like timingSafeEqual. Optionally, reject deliveries whose t timestamp is more than a few minutes old to guard against replay. Keep that window at 5 minutes or more: the timestamp is set when the event is created and reused on retries, so a retried delivery arrives with the original timestamp.

Delivery and retries

  • Respond with any 2xx within 10 seconds. That counts as delivered. Anything else (including a timeout after 10 seconds) counts as a failure. Do your processing after you respond, not before.
  • Failed deliveries retry twice: the first retry after 2 seconds, the second after 5 more. Only 5xx, 429, and network errors are retried; any other 4xx fails immediately without a retry.
  • Delivery is at-least-once. If your endpoint processed the event but the response timed out, the retry delivers a duplicate. Deduplicate on X-OmniSocials-Delivery; it stays the same across retries of one event.
  • Ordering is not guaranteed. Deliveries fire asynchronously and concurrently; a retrying delivery can arrive after a newer event. Order on the payload's created_at.
  • After the final failed attempt the event is dropped. It is not re-sent later, and there is no replay endpoint. The webhook records the failure: failure_count increments and last_failure (time, status, error) is stored; read both via GET /webhooks/:id. A webhook is never disabled automatically, so a recovered endpoint receives the next event normally. Reconcile missed events by polling GET /posts?status=.
  • Requests come with User-Agent: OmniSocials-Webhooks/1.0 and follow up to 3 redirects.

Local testing. Webhook URLs must be publicly reachable. For local development, expose your dev server with a tunnel (for example cloudflared tunnel or ngrok http 3000) and register the tunnel URL as the webhook.

Managing webhooks

All webhook endpoints require the webhooks:manage scope on your API key.

EndpointDescription
GET /webhooksList all webhooks for this key
GET /webhooks/:idGet a single webhook
PATCH /webhooks/:idUpdate URL, events, or is_active (re-enabling resets failure_count)
DELETE /webhooks/:idDelete a webhook
POST /webhooks/:id/rotate-secretRotate the signing secret

Rotating a secret immediately invalidates the old one. Update your verification code to use the new secret before rotating, or accept a short window of failed verifications.

Last modified on October 21, 2026
Docs for AI agentsPosts
On this page
  • Event catalog
  • Creating a webhook
  • Available events
  • Payload shape
    • Approval events
  • Verifying signatures
  • Delivery and retries
  • Managing webhooks
JSON
JSON
JSON
Javascript