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:
| Card | What it receives |
|---|---|
| Slack | Real score drops, as a message (setup guide) |
| Microsoft Teams | Real score drops, as an Adaptive Card |
| Zapier | Both events, as a flat JSON payload (setup guide) |
| Custom Webhook | Both 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).
- In Teams, open the channel that should receive alerts, click the … next to its name and choose Workflows.
- 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. - Copy the URL Teams shows — it starts with
https://…logic.azure.com/…orhttps://…webhook.office.com/…— and click Done. - In AEOTrack, open Integrations → Microsoft Teams, paste the URL and click Connect.
- 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_dropfires after a check when the drop is real (outside the confidence range).kindisaeoorgeo.check_completedfires after every scheduled check finishes, with that run's scores (nulluntil 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 noeventfield, so a handler should treat a missingeventas 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
automationcapability, 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.