Webhooks
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.
Subscribe to any combination of events per webhook endpoint. All events fire across all connected profiles and all platforms automatically.
| Event | When it fires |
|---|---|
| post.scheduled | Post entered the queue and is waiting for its scheduled time |
| post.publishing | Post is being submitted to the platform right now |
| post.published | Post successfully published — includes platform post ID and URL |
| post.failed | Post failed after all retries — includes error code and platform message |
| post.cancelled | Post was cancelled before publish via the API |
| comment.created | New comment on any post from a connected profile |
| message.created | New DM, mention, or comment thread on a connected profile |
| message.updated | Existing message status changed (e.g., marked read by platform) |
One endpoint registration covers every connected profile on every platform. The returned secret is used to verify signatures on all incoming requests.
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 payloadsEvery Aether webhook includes an x-aether-signature header. Verify it before processing — use timingSafeEqual to prevent timing attacks.
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 keyEvery payload includes the event type, webhook ID, delivery timestamp, and a typed data object. The shape is documented in the OpenAPI spec.
// 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
}
}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.
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.
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.
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.
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.
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.
HMAC-signed · auto-retry · dead-letter queue · 7 platforms · free tier.
Get your free API key →