How can AEOTrack help you?

September 23, 2026

Microsoft Teams, custom webhooks and the Zapier events

The connections on the Integrations page

Under Integrations there are four outbound connections that all work the same way — paste a URL, click Connect, click Test — and all sit on Pro, Agency and Enterprise:

CardWhat it receives
SlackReal score drops, as a message (setup guide)
Microsoft TeamsReal score drops, as an Adaptive Card
ZapierBoth events, as a flat JSON payload (setup guide)
Custom WebhookBoth events, as a flat JSON payload

"Real" means a drop that is outside the score's confidence range, judged after each check. Slack and Teams are human channels, so they get drops only; a channel that pings on every routine check gets muted, and muting loses the alerts that matter. Zapier and custom webhooks are for systems, so they also get the steady heartbeat of every completed scheduled check.

Microsoft Teams

Teams receives webhooks through the Workflows app (the older "Incoming Webhook" connector is retired by Microsoft).

  1. In Teams, open the channel that should receive alerts, click the next to its name and choose Workflows.
  2. Pick the template "Post to a channel when a webhook request is received", name it (for example AEOTrack alerts), confirm the team and channel, and click Add workflow.
  3. Copy the URL Teams shows — it starts with https://…logic.azure.com/… or https://…webhook.office.com/… — and click Done.
  4. In AEOTrack, open Integrations → Microsoft Teams, paste the URL and click Connect.
  5. Click Test. A test card appears in the channel within a few seconds. Connections are per website: repeat for each website that should post to the channel, or use one workflow for all of them.

Each alert is an Adaptive Card: the website, whether it was the AEO or GEO score, the previous and new score, the size of the drop, and a link back to the dashboard.

Custom webhook

Point the Custom Webhook card at any HTTPS endpoint you control that accepts a POST with a JSON body. Private and internal addresses are refused at connect time.

The two events

{
  "event": "score_drop",
  "domain": "acme.com",
  "kind": "aeo",
  "previous_score": 62.4,
  "new_score": 51.9,
  "drop": 10.5,
  "text": "Acme's aeo visibility score dropped from 62 to 52 (10 points)."
}
{
  "event": "check_completed",
  "domain": "acme.com",
  "aeo_score": 58.2,
  "geo_score": 44.0,
  "queries_checked": 25,
  "text": "Scheduled check finished for acme.com: AEO 58 / GEO 44 (25 questions checked)."
}
  • score_drop fires after a check when the drop is real (outside the confidence range). kind is aeo or geo.
  • check_completed fires after every scheduled check finishes, with that run's scores (null until a website has a score) and how many questions were checked.
  • The Test button sends {"text": "Test message from AEOTrack — this integration is connected."} with no event field, so a handler should treat a missing event as a ping.
  • Deliveries time out after 10 seconds and are not retried. Answer with a 2xx quickly and do the work afterwards.
  • The same payloads are what Zapier's Test Trigger discovers, so a Zap can map each field into its next step and filter on event.

Verifying the signature

When you connect, AEOTrack shows a signing secret once. Every delivery carries an X-AEOTrack-Signature header: the hex HMAC-SHA256 of the raw request body, keyed with that secret. Verify it before trusting a payload:

// Node (Express with express.raw({ type: "application/json" }))
const crypto = require("crypto");
function verify(req, secret) {
  const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
  const given = req.get("X-AEOTrack-Signature") || "";
  return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
# Python
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(header or "", expected)

Compute the HMAC over the exact bytes received — re-serialising the JSON first will change the whitespace and break the match. To rotate the secret, Disconnect and connect again: a new secret is generated on every connect. Connections made before signing existed have no secret and are delivered unsigned.

Plan and account rules

  • The four cards need the automation capability, and the connections re-check the plan at every send, so a connection made on Pro stops posting if the account drops to a plan without it.
  • The alert rules under Settings → Automation → Alerts are separate: they have their own threshold and their own Slack or email destinations, and email rules also report improvements. The Automation guide explains how the two relate.
  • Delivery is best-effort: a hook that is down does not hold up the in-app notification or any other channel.