Webhooks

Receive real-time events when AyeWatch detects changes in your monitored topics.

Setup

Configure your webhook endpoint URL in the Developer dashboard → Webhook. Your endpoint must:

  • Be publicly accessible over HTTPS
  • Respond with a 2xx status within 10 seconds
  • Accept POST requests with a JSON body
Note: AyeWatch does not currently retry failed webhook deliveries. Ensure your endpoint is reliable and responds quickly.

When webhooks fire

AyeWatch sends a webhook only when new content is detected for a monitored topic. No request is sent when a topic is checked but nothing has changed.

Webhook Payload

AyeWatch sends a POST request with a JSON body to your endpoint:

Example payload
json
{
  "topic_id": 1042,
  "topic_name": "https://openai.com/news",
  "update_id": 7891,
  "headline": "GPT-5 announced",
  "content": "OpenAI today announced the release of GPT-5...",
  "content_detail": "...",
  "created_at": "2026-03-08T12:00:00Z"
}
FieldTypeDescription
topic_idnumberInteger ID of the monitored topic (matches the id from the Topics API).
topic_namestringName or URL of the topic as set on creation.
update_idnumberInteger ID of this specific update.
headlinestring | undefinedShort AI-generated summary of the detected change.
contentstring | undefinedFull content of the update.
content_detailstring | undefinedAdditional detail about the update.
created_atstring (ISO 8601)Timestamp when the update was created.

Signature Verification

Every webhook request includes an X-AyeWatch-Signature header containing an HMAC-SHA256 signature signed with your webhook secret. Verify against the raw request bytes — not a re-serialized version of the parsed payload.

Header format: X-AyeWatch-Signature: sha256=<hex_digest>

Always verify this signature before processing webhook events. Reject events that fail verification.

Node.js / Express
javascript
const crypto = require("crypto");

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  // Use timingSafeEqual to prevent timing attacks
  const expectedBuffer = Buffer.from(expected, "utf8");
  const signatureBuffer = Buffer.from(signature, "utf8");

  if (expectedBuffer.length !== signatureBuffer.length) return false;
  return crypto.timingSafeEqual(expectedBuffer, signatureBuffer);
}

// Express example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-ayewatch-signature"];
  const isValid = verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = JSON.parse(req.body);
  console.log("Update received for topic:", event.topic_id, event.headline);
  res.status(200).json({ received: true });
});
Python / FastAPI
python
import hashlib
import hmac

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

# FastAPI example
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.post("/webhook")
async def webhook(request: Request):
    raw_body = await request.body()
    signature = request.headers.get("x-ayewatch-signature", "")

    if not verify_webhook_signature(raw_body, signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = await request.json()
    print("Update received for topic:", event["topic_id"], event.get("headline"))
    return {"received": True}

Delivery Behavior

  • No retries: Failed deliveries (non-2xx or timeout) are not retried.
  • 10-second timeout: Requests that don't complete within 10 seconds are abandoned.
  • HTTPS only: Plain HTTP endpoints are not supported.
  • Order not guaranteed: Events may arrive out of order under load.
Login