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
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 Developer dashboard → 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.