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
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"
}| 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. |
| 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. |
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.