Webhooks
Receive real-time events when AyeWatch detects changes in your monitored topics.
Setup
Configure your webhook endpoint URL in the Notification settings → Webhook, or with the Webhook API below. Your endpoint must:
- Be publicly accessible over HTTPS
- Respond with a
2xxstatus within 10 seconds - Accept
POSTrequests with a JSON body
Webhook API
Manage the endpoint with your API key. Each account has one webhook, and it receives updates from every monitor in the account.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/webhook | Get the webhook. data is null when none is configured. |
| PUT | /api/v1/webhook | Create the webhook, or update the fields you send |
| POST | /api/v1/webhook/rotate-secret | Replace the signing secret |
PUT /api/v1/webhook
| Name | Type | Required | Description |
|---|---|---|---|
| webhook_url | string | To create | Public HTTPS URL. Private, loopback, and internal addresses are rejected. |
| is_active | boolean | No | false pauses delivery and true resumes it. New webhooks start active. |
Send at least one field. Omitted fields keep their current values, and changing the URL keeps the signing secret. Sending only is_active before a webhook exists returns 404.
curl -X PUT https://ayewatch.ai/api/v1/webhook \
-H "Authorization: Bearer aw_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"webhook_url": "https://example.com/hooks/ayewatch"}'{
"data": {
"id": "4f6c2a1e-8b0d-4c7a-9e35-1d2f6b8a7c90",
"webhook_url": "https://example.com/hooks/ayewatch",
"is_active": true,
"secret": "<64 hex characters>",
"created_at": "2026-10-06T12:00:00Z",
"updated_at": "2026-10-06T12:00:00Z"
}
}GET /api/v1/webhook and POST /api/v1/webhook/rotate-secret return the same object. Rotating before a webhook exists returns 404.
secret, which signs every delivery. Anyone holding one of your API keys can read it, so if a key leaks, revoke the key and rotate the secret.AyeWatch emails the account owner whenever the webhook URL is set or changed, whether from settings, the API, or MCP. Pausing, resuming, and rotating the secret do not send an email.
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 or with POST /api/v1/webhook/rotate-secret. 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.