Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.social-api.ai/llms.txt

Use this file to discover all available pages before exploring further.

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:
curl -X POST https://api.social-api.ai/v1/webhooks \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.example.com/webhooks/socapi",
    "events": ["comment.received", "dm.received", "review.received"]
  }'
Response (HTTP 201):
{
  "id": "wh_01HZ9X3Q4R5M6N7P8V2K0W1J",
  "url": "https://app.example.com/webhooks/socapi",
  "events": ["comment.received", "dm.received", "review.received"],
  "secret": "a3f8c2e1b9d4f7a6c5e0b3d2f1a8e7c4b9d6f3a2e5c8b1d4f7a0e3c6b9d2f5a8",
  "message": "Store the secret securely. It will not be shown again."
}
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.
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.

Event types

Events are grouped into three categories: inbox (incoming interactions), posts (post lifecycle transitions), and accounts (connection state).
EventCategoryWhen it fires
comment.receivedinboxA new comment arrives on any connected account
dm.receivedinboxA new direct message arrives (may include metadata.referral if the DM originated from an ad)
dm.sentinboxYour account sent a direct message (echo confirmation from Meta)
dm.referralinboxA user clicked an ad or referral link without sending a message. The interaction has empty content and includes metadata.referral
review.receivedinboxA new review arrives (Google Business Profile)
mention.receivedinboxYour account is mentioned on a supported platform
dm.status.sentinboxA direct message you sent was accepted by the platform
dm.status.deliveredinboxA direct message you sent was delivered to the recipient
dm.status.readinboxA direct message you sent was read by the recipient
dm.status.failedinboxA direct message you sent failed to deliver
post.scheduledpostsA post was created with a future scheduled_at and entered the scheduled state
post.publishedpostsA post finished publishing successfully on all targets
post.partialpostsA post published on some targets but failed on others
post.failedpostsA post failed to publish on all targets
post.updatedpostsA draft, scheduled, or failed post was edited
post.deletedpostsA post was deleted. The payload includes was_status so you know what state the post was in before removal
post.unpublishedpostsA published post was removed from one or more platforms (the post row stays unless every target is unpublished)
post.retriedpostsA failed or partial post is being retried. A subsequent post.published, post.partial, or post.failed event fires once the retry completes
account.connectedaccountsA social account finished connecting. Payload includes username, display name, profile picture, bio, and a public profile link
account.disconnectedaccountsA connected account was revoked by the user on the platform side (Meta deauthorize webhook, TikTok deauthorize webhook)
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.
{
  "event": "post.scheduled",
  "data": {
    "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "status": "scheduled",
    "scheduled_at": "2026-06-01T14:00:00Z",
    "targets": [
      { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram" },
      { "account_id": "acc_02HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "facebook" }
    ]
  }
}
{
  "event": "post.updated",
  "data": {
    "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "status": "scheduled",
    "changed_fields": ["text", "scheduled_at"]
  }
}
{
  "event": "post.deleted",
  "data": {
    "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "was_status": "published"
  }
}
{
  "event": "post.unpublished",
  "data": {
    "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "unpublished_count": 1
  }
}
When account_id is null on a post.unpublished event, the post was unpublished from every target.
{
  "event": "post.retried",
  "data": {
    "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "retried_count": 2
  }
}
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.
{
  "event": "account.connected",
  "data": {
    "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "brand_id": "br_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "platform": "instagram",
    "platform_user_id": "17841405793187218",
    "username": "acmecorp",
    "display_name": "ACME Corp",
    "profile_picture_url": "https://scontent.cdninstagram.com/...",
    "bio": "Official ACME account",
    "profile_url": "https://www.instagram.com/acmecorp/",
    "user_email": "owner@acme.com",
    "connected_at": "2026-05-25T14:30:00Z"
  }
}
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.

Payload format

Every webhook POST has a JSON body with an event field and a data field containing the full interaction object:
{
  "event": "comment.received",
  "data": {
    "id": "sapi_cmt_aW5zdGFncmFtOjE3ODQxNDA1",
    "type": "comment",
    "platform": "instagram",
    "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "author": {
      "id": "10215054824",
      "name": "Jane Smith",
      "avatar_url": "https://example.com/avatar.jpg"
    },
    "content": {
      "text": "Love this product!",
      "media": []
    },
    "received_at": "2026-03-01T14:30:00Z",
    "metadata": {}
  }
}
The data object is the same Interaction shape returned by GET /accounts/{id}/comments (and the equivalent DM, review, and mention endpoints). The id field is a stable SocialAPI interaction ID - see Interaction IDs.

Referral metadata

When a DM originates from an ad click (Instagram CTD or Facebook CTM ads), the interaction includes referral context in metadata.referral:
{
  "event": "dm.received",
  "data": {
    "id": "sapi_dm_aW5zdGFncmFtOm1pZC5yZWYx",
    "type": "dm",
    "platform": "instagram",
    "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
    "author": { "id": "17846744073097661" },
    "content": { "text": "I saw your ad!" },
    "received_at": "2026-03-01T14:30:00Z",
    "metadata": {
      "referral": {
        "ad_id": "120210572164750432",
        "source": "ADS",
        "type": "OPEN_THREAD",
        "ads_context_data": {
          "ad_title": "Spring Sale 25% Off",
          "photo_url": "https://scontent.xx.fbcdn.net/..."
        }
      }
    }
  }
}
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.

Request headers

Every webhook request includes:
HeaderValue
Content-Typeapplication/json
X-SocialAPI-Signaturesha256=<hmac-sha256-hex>
X-SocialAPI-EventThe event type, e.g. comment.received

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=.
import crypto from "crypto";

function verifySignature(secret, rawBody, signatureHeader) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

// Express example
app.post("/webhooks/socapi", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["x-socialapi-signature"];
  if (!verifySignature(process.env.SOCAPI_WEBHOOK_SECRET, req.body, sig)) {
    return res.status(401).send("Invalid signature");
  }
  const event = JSON.parse(req.body);
  // handle event.event and event.data
  res.sendStatus(200);
});
Use a constant-time comparison (timingSafeEqual / compare_digest / hmac.Equal) to prevent timing attacks. A simple string equality check is not safe.

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:
AttemptDelay
1Immediate
2~30 seconds
3~5 minutes
4~30 minutes
5~3 hours
After 5 failed attempts, the delivery is marked failed and no further retries occur.

Managing endpoints

List endpoints

curl https://api.social-api.ai/v1/webhooks \
  -H "Authorization: Bearer $SOCAPI_KEY"

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).
curl https://api.social-api.ai/v1/webhooks/{id} \
  -H "Authorization: Bearer $SOCAPI_KEY"

Update an endpoint

Change the URL, subscribed events, or toggle delivery on/off. All fields are optional; provide at least one.
curl -X PATCH https://api.social-api.ai/v1/webhooks/{id} \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'

Delete an endpoint

curl -X DELETE https://api.social-api.ai/v1/webhooks/{id} \
  -H "Authorization: Bearer $SOCAPI_KEY"
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.
EndpointDescription
GET /v1/webhooks/{id}/deliveriesList deliveries (filterable by status and event_type, cursor-paginated)
GET /v1/webhooks/{id}/deliveries/{did}Full delivery detail including payload and all retry attempts
POST /v1/webhooks/{id}/deliveries/{did}/retryRe-enqueue a failed delivery
POST /v1/webhooks/{id}/testSend a test payload to verify your endpoint is working
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
  • 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 the interaction id to deduplicate