Blog/API Guides

What is a webhook? A complete guide for developers

June 9, 2026·11 min readAPI Guides

A webhook is an HTTP callback that fires when an event happens — no polling required. Learn how webhooks work, how to secure them with HMAC signatures, and how to handle failures.

A webhook is an HTTP POST request that one server sends to another the moment a specific event occurs. Instead of your application repeatedly asking "did anything change?" (polling), the service tells you immediately when something changes.

Webhooks are sometimes called reverse APIs or HTTP callbacks. The pattern is everywhere: Stripe fires a webhook when a payment succeeds, GitHub fires one when a pull request is merged, Aether fires one when a social media post publishes. Your server registers a URL, and when the event happens, the service POSTs a JSON payload to that URL.

Polling vs webhooks — what's the difference?

Before webhooks, the only way to know if something had changed was to ask. Your server would call the other API every few seconds: "Did the payment go through? What about now? Now?" This is polling, and it has three problems:

  • Latency. You only know about an event as fast as your poll interval — often seconds or minutes behind.
  • Waste. 99% of poll requests return "nothing changed" — you're burning API quota and compute for no value.
  • Rate limits. High-frequency polling can get you rate-limited or banned by the service.

Webhooks invert the model. You register your URL once, and the service calls you — immediately, only when something actually happens. Zero wasted requests, sub-second latency, and no rate limit risk from polling.

How webhooks work — the 3-step process

Every webhook integration follows the same three steps:

  • Register. You tell the service: "POST to this URL when these events happen."
  • Event fires. Something happens on the service's side — a payment, a post, a message.
  • Your server handles it. The service sends a POST to your URL with a JSON body describing the event. You process it and respond with a 200 OK.

The most important rule: respond with 200 quickly.Most webhook providers have a timeout (typically 5–30 seconds). If your handler takes longer — because you're making database calls, sending emails, or doing heavy work — the provider marks the delivery as failed. Acknowledge first, process async: return 200, push the payload to a queue, handle it in a worker.

Building a webhook receiver

A webhook receiver is just an HTTP endpoint. Here's a minimal implementation in Node.js and Python that verifies the signature (explained next) and handles two event types:

// Node.js + Express — minimal webhook receiver
import express from "express";
import { createHmac, timingSafeEqual } from "crypto";

const app = express();

// IMPORTANT: use express.raw() to get the raw body for signature verification
app.use("/hooks", express.raw({ type: "application/json" }));

app.post("/hooks/aether", (req, res) => {
  const signature = req.headers["x-aether-signature"] as string;
  const secret    = process.env.WEBHOOK_SECRET!;

  // 1. Compute expected signature
  const expected = `sha256=${createHmac("sha256", secret).update(req.body).digest("hex")}`;

  // 2. Compare — use timingSafeEqual to prevent timing attacks
  if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature ?? ""))) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // 3. Parse and handle
  const payload = JSON.parse(req.body.toString());
  console.log("Event:", payload.event, "Post ID:", payload.data?.postId);

  res.status(200).json({ received: true });
});

app.listen(3000);
# Python + Flask — webhook receiver with HMAC verification
import hmac
import hashlib
import os
from flask import Flask, request, jsonify

app = Flask(__name__)

def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route("/hooks/aether", methods=["POST"])
def webhook():
    signature = request.headers.get("X-Aether-Signature", "")
    if not verify_signature(request.data, signature, os.environ["WEBHOOK_SECRET"]):
        return jsonify({"error": "Invalid signature"}), 401

    payload = request.get_json(force=True)
    event   = payload.get("event")

    if event == "post.published":
        print("Published:", payload["data"]["postId"])
    elif event == "post.failed":
        print("Failed:", payload["data"]["error"]["message"])

    return jsonify({"received": True}), 200

Verifying webhook signatures

Any server on the internet can POST to your webhook URL. Without verification, you have no way to confirm the request actually came from the service you registered with — an attacker could send fake event payloads.

Most webhook providers solve this with HMAC-SHA256 signatures. When you register a webhook, you receive a secret. The provider computes a signature by running HMAC-SHA256 over the raw request body using your secret, then includes it in a request header. You do the same computation locally and compare:

  • Compute: sha256=HMAC(secret, raw_body)
  • Compare to the x-signature header value
  • Use timingSafeEqual — never use === for secret comparison

Two critical implementation details: use the raw request body (before JSON parsing — parsed objects stringify differently), and always use a timing-safe comparison function. Regular string equality leaks information about how many characters match, which enables timing attacks.

Registering a webhook with Aether

Aether fires webhooks on every social media event — post published, post failed, new comment, new DM. One endpoint registration covers all 7 platforms and all connected profiles:

import Aether from "aether";

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

// Register your endpoint — one call covers all platforms and all event types
const webhook = await aether.webhooks.create({
  url: "https://your-app.com/hooks/aether",
  events: ["post.published", "post.failed", "message.created"],
});

// Store webhook.secret in your environment — needed for signature verification
console.log(webhook.secret); // whsec_abc123...

What happens when a webhook delivery fails?

Your server goes down. You deploy and get a 502 for 30 seconds. The webhook fires exactly then. What happens next depends on the provider's retry policy.

Aether retries failed deliveries with exponential backoff over 24 hours: immediately, then 5 minutes, 30 minutes, 2 hours, 6 hours, and 12 hours. After all retries are exhausted, the event moves to a dead-letter queue in your dashboard where you can replay it manually.

This is why the "return 200 immediately" rule matters. If you do heavy processing synchronously and occasionally time out, you'll trigger unnecessary retries — potentially processing the same event multiple times. Design your handler to be idempotent: receiving the same event twice should produce the same result as receiving it once. Use the event's unique ID to deduplicate.

Debugging webhooks locally

Your localhostisn't reachable from the internet. To test webhooks during development, use ngrok to create a public tunnel to your local server:

# Test your webhook locally with ngrok
# 1. Expose your local server
ngrok http 3000

# 2. Use the ngrok URL as your webhook endpoint when developing
# https://abc123.ngrok-free.app/hooks/aether

# 3. Trigger a test delivery from the Aether dashboard
# Settings → Webhooks → [your webhook] → Send test event

Most webhook providers (including Aether) let you send test events from the dashboard without needing a real event to fire. Use this to verify your signature logic before going live.

Webhooks vs polling vs long polling — when to use each

  • Webhooks: Use when the service supports them and you need real-time notification. Best default for event-driven systems.
  • Polling: Use when the service doesn't offer webhooks, or when you need to query state on demand. Keep intervals long (5+ min) and cache results.
  • Long polling: The client holds a request open until the server has data to return. Simulates push over HTTP. More complex than webhooks, less efficient at scale.
  • Server-sent events / WebSockets: Use for browser-to-server real-time communication. Not relevant for server-to-server webhook use cases.

Next steps

Ready to build?

One API for Instagram, TikTok, LinkedIn, Facebook, YouTube, Threads, and Reddit. Free tier, no credit card required.

Get started free →