Docs
Docs/API Reference/Webhooks

Webhooks

Receive real-time notifications for events

Webhooks

Webhooks let you receive real-time HTTP notifications when events happen in your Octopost account. Instead of polling the API for changes, register a webhook URL and Octopost will send a POST request to it whenever a subscribed event occurs.

How Webhooks Work

  1. You register a webhook endpoint URL and select which events to subscribe to.
  2. When a subscribed event occurs, Octopost sends an HTTP POST request to your URL with the event payload.
  3. Your server responds with a 2xx status code to acknowledge receipt.
  4. If delivery fails, Octopost retries with exponential backoff.

Events

EventDescription
post.publishedA post was successfully published to at least one platform.
post.failedA post failed to publish on all platforms.
post.scheduledA post was scheduled for future publishing.
account.connectedA new social media account was connected.
account.disconnectedA social media account was disconnected or its token expired.

The Webhook Object

{
  "id": "wh_abc123",
  "url": "https://example.com/webhooks/octopost",
  "events": ["post.published", "post.failed"],
  "secret": "whsec_abc123def456",
  "is_active": true,
  "created_at": "2026-04-01T10:00:00Z",
  "updated_at": "2026-04-01T10:00:00Z"
}

Event Payload

Every webhook delivery sends a JSON payload with this structure:

{
  "id": "evt_abc123",
  "type": "post.published",
  "created_at": "2026-04-03T12:05:00Z",
  "data": {
    "post": {
      "id": "post_def456",
      "content": "Hello from Octopost!",
      "platforms": ["twitter", "bluesky"],
      "status": "published",
      "platform_results": {
        "twitter": {
          "success": true,
          "post_id": "1234567890",
          "post_url": "https://x.com/user/status/1234567890"
        },
        "bluesky": {
          "success": true,
          "post_id": "at://did:plc:abc/app.bsky.feed.post/xyz",
          "post_url": "https://bsky.app/profile/user.bsky.social/post/xyz"
        }
      },
      "published_at": "2026-04-03T12:05:00Z"
    }
  }
}

Event-Specific Data

post.published and post.failed: data.post contains the full post object including platform_results.

post.scheduled: data.post contains the post object with scheduled_for set.

account.connected: data.account contains the connected account object.

account.disconnected: data.account contains the disconnected account object with is_active: false.


Managing Webhooks

Create a Webhook

POST /webhooks

Request Body

FieldTypeRequiredDescription
urlstringYesThe HTTPS URL to receive webhook deliveries. Must use HTTPS.
eventsstring[]YesArray of event types to subscribe to.

Example

curl -X POST https://api.octopost.ink/v1/webhooks \
  -H "Authorization: Bearer oct_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/octopost",
    "events": ["post.published", "post.failed"]
  }'
{
  "id": "wh_abc123",
  "url": "https://example.com/webhooks/octopost",
  "events": ["post.published", "post.failed"],
  "secret": "whsec_abc123def456",
  "is_active": true,
  "created_at": "2026-04-03T16:00:00Z",
  "updated_at": "2026-04-03T16:00:00Z"
}

The secret is returned only on creation. Store it securely -- you will need it to verify webhook signatures.

List Webhooks

GET /webhooks
curl https://api.octopost.ink/v1/webhooks \
  -H "Authorization: Bearer oct_live_abc123"
{
  "webhooks": [
    {
      "id": "wh_abc123",
      "url": "https://example.com/webhooks/octopost",
      "events": ["post.published", "post.failed"],
      "is_active": true,
      "created_at": "2026-04-03T16:00:00Z",
      "updated_at": "2026-04-03T16:00:00Z"
    }
  ]
}

Note: The secret is not returned in list or get responses.

Update a Webhook

PUT /webhooks/:id
FieldTypeDescription
urlstringUpdated endpoint URL.
eventsstring[]Updated event subscriptions.
is_activebooleanEnable or disable the webhook.
curl -X PUT https://api.octopost.ink/v1/webhooks/wh_abc123 \
  -H "Authorization: Bearer oct_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["post.published", "post.failed", "post.scheduled"]
  }'

Delete a Webhook

DELETE /webhooks/:id
curl -X DELETE https://api.octopost.ink/v1/webhooks/wh_abc123 \
  -H "Authorization: Bearer oct_live_abc123"

Returns 204 No Content on success.


Signature Verification

Every webhook delivery includes an X-Octopost-Signature header containing an HMAC-SHA256 signature. Verify this signature to confirm the request came from Octopost and was not tampered with.

The signature is computed over the raw request body using your webhook secret as the key.

Verification Steps

  1. Extract the X-Octopost-Signature header from the request.
  2. Compute the HMAC-SHA256 of the raw request body using your webhook secret as the key.
  3. Compare the computed signature to the header value using a constant-time comparison.

Example: Node.js / TypeScript

import crypto from "crypto";

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
const isValid = verifyWebhookSignature(
  rawBody,
  request.headers["x-octopost-signature"],
  "whsec_abc123def456"
);

if (!isValid) {
  return new Response("Invalid signature", { status: 401 });
}

Example: Python

import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Retry Policy

If your endpoint does not respond with a 2xx status code within 30 seconds, the delivery is considered failed and Octopost retries with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry12 hours

After 5 failed retries, the delivery is marked as failed and no further attempts are made. If a webhook consistently fails (10 consecutive failed deliveries across events), the webhook is automatically deactivated and you are notified via email.

You can view delivery history and manually retry failed deliveries from the Octopost Dashboard.


Best Practices

  • Respond quickly. Return a 200 immediately, then process the event asynchronously. Do not perform long-running work in the webhook handler.
  • Handle duplicate deliveries. Use the id field in the event payload to deduplicate. In rare cases, the same event may be delivered more than once.
  • Verify signatures. Always verify the X-Octopost-Signature header before trusting the payload.
  • Use HTTPS. Webhook URLs must use HTTPS. HTTP endpoints are rejected.