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, or with the Webhook API below. 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.

Webhook API

Manage the endpoint with your API key. Each account has one webhook, and it receives updates from every monitor in the account.

MethodPathDescription
GET/api/v1/webhookGet the webhook. data is null when none is configured.
PUT/api/v1/webhookCreate the webhook, or update the fields you send
POST/api/v1/webhook/rotate-secretReplace the signing secret

PUT /api/v1/webhook

NameTypeRequiredDescription
webhook_urlstringTo createPublic HTTPS URL. Private, loopback, and internal addresses are rejected.
is_activebooleanNofalse 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.

Request
shell
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"}'
Response, 200 OK
json
{
  "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.

Note: Responses include 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:

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