Skip to content

Webhooks

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

Setup

Configure your webhook endpoint URL in the Notification settings → 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: Failed deliveries are retried with exponential backoff for up to 10 attempts. Make handlers idempotent because a delivery may be attempted more than once.

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. Use it to deduplicate retried deliveries.
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.
novelty_classstring | undefinedHow new this is relative to what was already reported on the topic.
scorenumber | undefinedInternal confidence in the update's significance.
evidencearray | undefinedSupporting quotes with their source URLs: [{ quote, source_url }].
triggerstring | undefinedWhich of the topic's triggers fired, when the update came from one.

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

  • Retries: Non-2xx responses and timeouts are retried with exponential backoff, up to 10 attempts.
  • 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.
  • Additive payloads: New fields may be added over time. Ignore any you don't recognise rather than rejecting the request.

Rotating Your Secret

You can rotate the signing secret from the Notification settings → Webhook. Rotation takes effect immediately, there is no overlap window, so deliveries signed with the new secret will fail verification until your receiver is updated. Deploy the new secret first, or rotate during a window where a few retried deliveries are acceptable.