Skip to main content
SocialAPI.ai webhooks deliver real-time social media events (new comments, direct messages, and mentions) across every connected network through one signed, retried webhook stream, so you do not poll each platform separately. Instead of polling the API, register a webhook endpoint and SocialAPI will POST a signed payload to your server whenever a new comment, DM, review, or mention arrives, or whenever one of your posts changes lifecycle state (scheduled, published, deleted, etc.).

Register an endpoint

Endpoints can be registered in the dashboard under WebhooksAdd Endpoint, or via the API:
Response (HTTP 201):
The secret is returned once. Store it immediately in your environment. It cannot be retrieved again. You use it to verify incoming signatures.
Endpoint URLs must use HTTPS. HTTP URLs are rejected with 400.
The events array is required. Sending an empty array or omitting it returns 400 field_invalid.
When you create an endpoint, SocialAPI sends a verification ping to your URL. Your server must respond with a 2xx status code or the endpoint will not be saved. Make sure your server is running and reachable before registering.
The verification ping arrives as a webhook.test event, signed with the endpoint’s new secret. Because the secret is only revealed to you after verification succeeds, your endpoint cannot verify this first signature. Respond 2xx to requests with X-SocialAPI-Event: webhook.test without checking the signature, or registration will fail with webhook_verification_failed.

Event types

Events are grouped into three categories: inbox (incoming interactions), posts (post lifecycle transitions), and accounts (connection state). You can subscribe to any combination. Fetch the authoritative list with GET /v1/webhooks/events and subscribe to everything by passing the full set.
The authoritative list of event types is available at GET /v1/webhooks/events. It returns each event’s description and category, so dashboard UIs and integrations can render the catalog without hard-coding it.

Post lifecycle payloads

Each post lifecycle event carries a small payload with the post_id and event-specific fields.
When account_id is null on a post.unpublished event, the post was unpublished from every target.
post.published, post.partial, and post.failed share a { post_id, status, results } payload, where results is the same per-target outcome array returned by POST /v1/posts.

account.connected payload

Fires once per connected account after a successful OAuth exchange or direct connection. Use this to render the connected profile in your UI or kick off post-connect work.
profile_url is built per platform from username (Instagram, Threads, TikTok, X) or platform_user_id (Facebook, LinkedIn, YouTube, Google Business Profile). It is empty when neither is available.

page.removed payload

Fires when a single page on a multi-page account (for example a Facebook Page) is detected as removed during a periodic refresh. The page is deactivated and stops appearing in page listings. Other pages on the same account stay active.

account.disconnected payload

Fires in two cases. A platform-side revocation (the user removes the app) carries no reconnect_required flag and the account is deleted. A reconnection prompt carries reconnect_required: true: the account row still exists but its status is set so that calls signal it needs reconnecting. The reason is all_pages_removed (the last remaining page was removed) or access_revoked (the account token or permissions were revoked, which fails every page at once).

Payload format

Every webhook POST has a JSON body with an event field and a data field containing the full interaction object:
The data object is the same Interaction shape returned by GET /accounts/{id}/comments (and the equivalent DM and mention endpoints). Google review events are the exception: see Review event payloads. The id field is a stable SocialAPI interaction ID - see Interaction IDs. For platform-originated events, the body also includes a top-level raw_payload field alongside data: the verbatim per-event webhook fragment as the platform sent it to us. Use it when you need a platform detail the normalized data object does not carry (for example, app_id on Facebook message echoes to distinguish API-sent messages from replies typed in the Page inbox). The shape of raw_payload is defined by the platform and can change with platform API versions, so prefer data whenever it has what you need. The dm.status.* events use a smaller payload instead of the full interaction object. On Instagram and Facebook, data contains status, mids (the platform message IDs the receipt covers), and recipient_id. On WhatsApp, data contains status, recipient_id, message_id (the WhatsApp wamid), and id: the same SocialAPI message ID returned by POST /v1/inbox/conversations/{id}/messages and by the send-template endpoint’s message_id, so you can match a receipt to the row you stored without guessing by recency when several messages are in flight.

Review event payloads

Google Business Profile review events do not follow the full-interaction shape above. Google’s notification carries only a resource name, never the review’s content, so data holds the identifiers plus a review object we read for you:
The review object is the same shape as an item in GET /v1/inbox/reviews/{account_id}, so one parser handles both. On review.updated with change: "reply_rejected", review.reply.policy_violation explains why the reply was refused.
review is best-effort and can be absent. We read it while handling the notification, and a review deleted between the notification and the read, or a token that has since been revoked, leaves us with nothing to send. The event is still delivered with account_id, review_id, and change, so treat review as optional and fall back to GET /v1/inbox/reviews/{account_id} when it is missing.

Post permalink

Instagram comment.received events include a permalink field inside data: the public URL of the post the comment was left on. When we have not seen the post before, we fetch it from Instagram while handling the webhook, so the first comment on a newly published post normally carries the link too. The field is best-effort. If the post cannot be fetched (it was deleted, or the fetch times out), the event is still delivered with permalink omitted. Other platforms do not set it: on Facebook, comment webhooks carry no permalink, and a post’s URL must be read from the posts API.

Fields that are not always present

platform_post_id is not sent on every comment event. Instagram and Facebook comments include it; Threads replies do not, and carry parent_id inside metadata instead. Treat the field as optional. On comment.received, raw_payload is always the verbatim Meta change entry (the single object from the platform’s changes array, not the whole envelope), because every platform that produces comment events is Meta-based. It sits at the top level of the body, alongside data rather than inside it.

Referral metadata

When a DM originates from an ad click (Instagram CTD or Facebook CTM ads), the interaction includes referral context in metadata.referral:
Only non-empty referral fields are included. dm.referral events (standalone ad clicks without a message) have empty content.text and always include metadata.referral.

Postback metadata

When a user taps a quick reply or a postback button you sent (see interactive messages), you receive a dm.postback event. The payload you defined and the tapped button’s title are carried in metadata:
When the tap arrives through an ig.me link or icebreaker, metadata.referral is also included. URL buttons (type: url) open the link in the in-app browser and do not generate a webhook.

Request headers

Every webhook request includes:
X-SocialAPI-Delivery is present on real event deliveries. Verification pings and test deliveries carry the signature, timestamp, and event headers but no delivery ID.

Verifying signatures

Always verify the X-SocialAPI-Signature header before processing a webhook. This confirms the payload came from SocialAPI and was not tampered with. The signature is HMAC-SHA256 of the raw request body, using your endpoint secret as the key, prefixed with sha256=.
Use a constant-time comparison (timingSafeEqual / compare_digest / hmac.Equal) to prevent timing attacks. A simple string equality check is not safe.

Replay protection

The v1 signature proves authenticity but not freshness: a captured request could be replayed later and would still verify. To bound replay, use the v2 scheme:
  1. Read X-SocialAPI-Timestamp and reject the request if it is older than your tolerance window (5 minutes is a sensible default).
  2. Compute HMAC-SHA256 over the string <timestamp>.<raw body> (the timestamp header value, a literal dot, then the raw request body) with your endpoint secret, prefix with sha256=, and compare it to X-SocialAPI-Signature-V2 in constant time.
Because the timestamp is inside the signed message, an attacker cannot extend the window by editing the header.
For exact once-only processing, additionally record X-SocialAPI-Delivery IDs you have already handled and skip duplicates. The ID is stable across retries of the same delivery, so this also deduplicates legitimate retries after a timeout.
The timestamp reflects the current delivery attempt, so a retry of an old event carries a fresh timestamp and still passes the window check. Use the delivery ID for deduplication; use the timestamp to reject replays of captured requests.

Responding to webhooks

Your endpoint must return an HTTP 2xx status within 10 seconds. The webhook HTTP client enforces a 10-second timeout per delivery attempt. Any non-2xx response or a timeout is treated as a delivery failure. Return 200 immediately and process the event asynchronously if your handler does heavier work.

Retry behavior

Failed deliveries are automatically retried up to 5 attempts with exponential backoff: After 5 failed attempts, the delivery is marked failed and no further retries occur.

Past-due accounts

Outbound delivery stops entirely while a subscription is past due. Events that occur during the block are dropped: no delivery is attempted, no delivery record is created, and nothing is replayed once the invoice is paid. Ingestion continues throughout, so the underlying comments, messages, and other events are still recorded. Query GET /v1/events after access is restored to see what happened during the gap. See billing.past_due for the full behavior.

Managing endpoints

List endpoints

Get endpoint details

Retrieve a single endpoint with delivery statistics (delivered/failed counts for the last 24 hours and 7 days). The secret is shown as a hint (last 4 characters only).

Update an endpoint

Change the URL, subscribed events, or toggle delivery on/off. All fields are optional; provide at least one.

Delete an endpoint

Deleting an endpoint stops all future deliveries immediately. In-flight jobs already queued may still attempt delivery once.

Delivery monitoring

Every webhook delivery is recorded with its status, HTTP response code, and duration. You can inspect delivery history, view individual attempts, send test payloads, and retry failed deliveries. See the API Reference for full request and response schemas.

Security best practices

  • Always verify signatures - never trust a webhook payload without checking X-SocialAPI-Signature (or, preferably, X-SocialAPI-Signature-V2)
  • Bound replay - prefer the v2 signature with a timestamp tolerance window (see Replay protection)
  • Store your secret in an environment variable - never hardcode it or commit it to source control
  • Use HTTPS - HTTP endpoints are rejected at registration time
  • Respond quickly - return 200 before doing heavy processing to avoid timeouts and spurious retries
  • Make handlers idempotent - retries mean the same event may arrive more than once; use X-SocialAPI-Delivery (stable across retries) or the interaction id to deduplicate