Webhooks

Real-time social media webhooks across every platform

Know the moment a post publishes, fails, or a new message arrives — across all 7 platforms from a single webhook registration. HMAC-signed payloads, automatic retry, dead-letter queue. No polling required.

Webhook event types

Subscribe to any combination of events per webhook endpoint. All events fire across all connected profiles and all platforms automatically.

EventWhen it fires
post.scheduledPost entered the queue and is waiting for its scheduled time
post.publishingPost is being submitted to the platform right now
post.publishedPost successfully published — includes platform post ID and URL
post.failedPost failed after all retries — includes error code and platform message
post.cancelledPost was cancelled before publish via the API
comment.createdNew comment on any post from a connected profile
message.createdNew DM, mention, or comment thread on a connected profile
message.updatedExisting message status changed (e.g., marked read by platform)

Register a webhook — one call, all platforms

One endpoint registration covers every connected profile on every platform. The returned secret is used to verify signatures on all incoming requests.

register-webhook.ts
import Aether from "aether";

const aether = new Aether({ apiKey: process.env.AETHER_API_KEY });

// Register your webhook endpoint once — covers all platforms
const webhook = await aether.webhooks.create({
  url: "https://your-app.com/hooks/aether",
  events: [
    "post.published",
    "post.failed",
    "comment.created",
    "message.created",
  ],
});
// → { id: "wh_xyz", url: "...", events: [...], secret: "whsec_..." }
// Store the secret — you'll use it to verify incoming payloads

Verify signatures and handle events

Every Aether webhook includes an x-aether-signature header. Verify it before processing — use timingSafeEqual to prevent timing attacks.

route.ts (Next.js App Router)
import { createHmac, timingSafeEqual } from "crypto";

// Verify the webhook signature on every incoming request
function verifyAetherWebhook(
  rawBody: string,
  signature: string,
  secret: string,
): boolean {
  const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
  return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

// Next.js App Router example
export async function POST(req: Request) {
  const rawBody = await req.text();
  const signature = req.headers.get("x-aether-signature") ?? "";

  if (!verifyAetherWebhook(rawBody, signature, process.env.WEBHOOK_SECRET!)) {
    return new Response("Unauthorized", { status: 401 });
  }

  const payload = JSON.parse(rawBody);

  switch (payload.event) {
    case "post.published": {
      const { postId, platform, platformPostId, platformPostUrl } = payload.data;
      // Update your DB, notify users, trigger downstream workflows
      await db.posts.update({ where: { id: postId }, data: { platformPostId, platformPostUrl } });
      break;
    }
    case "post.failed": {
      const { postId, platform, error } = payload.data;
      await notifyOwner(postId, `Post failed on ${platform}: ${error.message}`);
      break;
    }
    case "message.created": {
      const { messageId, platform, from, text } = payload.data;
      // Route to AI inbox agent, support queue, etc.
      await inboxQueue.add({ messageId, platform, from, text });
      break;
    }
  }

  return new Response("OK", { status: 200 });
}

Free tier · 3 accounts · no credit card

Get your free API key

Payload examples

Every payload includes the event type, webhook ID, delivery timestamp, and a typed data object. The shape is documented in the OpenAPI spec.

payload examples
// Example: post.published payload
{
  "event": "post.published",
  "webhookId": "wh_abc123",
  "deliveredAt": "2026-06-15T09:00:14Z",
  "data": {
    "postId": "post_xyz789",
    "platform": "instagram",
    "profileId": "ig_abc123",
    "platformPostId": "17890123456789",
    "platformPostUrl": "https://www.instagram.com/p/ABC123/",
    "publishedAt": "2026-06-15T09:00:12Z"
  }
}

// Example: post.failed payload
{
  "event": "post.failed",
  "webhookId": "wh_abc123",
  "deliveredAt": "2026-06-15T09:00:45Z",
  "data": {
    "postId": "post_xyz789",
    "platform": "tiktok",
    "profileId": "tt_xyz789",
    "error": {
      "code": "68002",
      "message": "Permission denied: missing video.publish scope",
      "retryable": false
    },
    "retriesExhausted": 5
  }
}

Frequently asked questions

What is a social media webhook?+

A webhook is an HTTP callback your server registers to receive real-time notifications when events happen. Instead of polling an API repeatedly to check if a post published, a webhook fires a POST request to your endpoint the moment the event occurs. Aether webhooks notify you when posts publish, fail, or are cancelled, and when new comments or DMs arrive on any connected profile.

How do I verify Aether webhook signatures?+

Every Aether webhook request includes an x-aether-signature header with a sha256= prefix. Compute HMAC-SHA256 of the raw request body using your webhook secret, prepend sha256=, and compare using a timing-safe comparison. The code example above shows the full implementation. Never compare the signature with a regular string equality check — use timingSafeEqual to prevent timing attacks.

What happens if my webhook endpoint is down?+

Aether retries failed webhook deliveries with exponential backoff over 24 hours: immediately, 5 minutes, 30 minutes, 2 hours, 6 hours, then 12 hours. After all retries, the event is moved to a dead-letter queue visible in your dashboard. You can replay dead-letter events manually or via the API.

Can I receive webhooks for specific platforms only?+

Webhooks fire for all connected profiles on all platforms. You can filter by platform or profileId in your handler using the data.platform and data.profileId fields in the payload. Platform-scoped webhook registration is on the roadmap.

How many webhooks can I register?+

Up to 10 webhook endpoints per organization on the free tier. Each webhook can subscribe to any combination of event types. You can use a single endpoint for all events and branch inside your handler, or register separate endpoints per event type.

What's the difference between comment.created and message.created?+

comment.created fires when a new comment appears on a post from a connected profile. message.created fires for DMs (direct messages) and mentions. On Instagram, DMs require the instagram_manage_messages permission. On Reddit, message.created covers both DMs and post replies. See the Inbox API for reading and replying to these messages.

Real-time events. No polling. No missed updates.

HMAC-signed · auto-retry · dead-letter queue · 7 platforms · free tier.

Get your free API key →