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
2xxstatus within 10 seconds - Accept
POSTrequests with a JSON body
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:
{
"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"
}| Field | Type | Description |
|---|---|---|
| topic_id | number | Integer ID of the monitored topic (matches the id from the Topics API). |
| topic_name | string | Name or URL of the topic as set on creation. |
| update_id | number | Integer ID of this specific update. Use it to deduplicate retried deliveries. |
| headline | string | undefined | Short AI-generated summary of the detected change. |
| content | string | undefined | Full content of the update. |
| content_detail | string | undefined | Additional detail about the update. |
| created_at | string (ISO 8601) | Timestamp when the update was created. |
| novelty_class | string | undefined | How new this is relative to what was already reported on the topic. |
| score | number | undefined | Internal confidence in the update's significance. |
| evidence | array | undefined | Supporting quotes with their source URLs: [{ quote, source_url }]. |
| trigger | string | undefined | Which 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.
X-AyeWatch-Signature: sha256=<hex_digest>Always verify this signature before processing webhook events. Reject events that fail verification.
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 });
});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.