Webhooks
Outgoing webhooks notify your system about platform events in real time — instead of polling
the API, you receive a POST at a URL of your choice.
Webhooks are available in the Commerce packages and up. They're configured by the account administrator; each webhook has a target URL, an optional signing secret and a list of subscribed events (a test delivery is available to verify your receiver).
Events
| Event | When |
|---|---|
visibility.check_completed | after a batch visibility check finishes (e.g. nightly monitoring) |
content.published | after content is published to a CMS (WordPress / Webflow) |
alert.triggered | when an alert fires (e.g. SoV drop, new competitor) |
attribution.order_synced | after an order is ingested via the API (POST /v1/orders) |
Delivery format
A POST with a JSON body:
{
"event": "alert.triggered",
"timestamp": "2026-07-22T06:15:00+00:00",
"workspace_id": "…",
"tenant_id": "…",
"data": { /* event-specific details */ }
}
Headers:
X-GEO-Event— the event name,X-GEO-Signature— asha256=<hex>signature (when a secret is set),User-Agent: GEO-Platform-Webhooks/1.0.
Verifying the signature
The signature is an HMAC-SHA256 of the raw request body, keyed with the webhook secret:
import hashlib, hmac
def verify(secret: str, body: bytes, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
Compute the HMAC over the body bytes exactly as received — before any JSON parsing (re-serialization changes the bytes and invalidates the signature).
Receiver requirements
- Respond with a 2xx code — anything else counts as a failed delivery.
- Respond fast (10 s limit) — accept the event, defer processing to a queue.
- Be idempotent — design the receiver so a repeated event doesn't duplicate effects.
FAQ
How do I separate staging from production? Use separate receiver URLs; the test delivery lets you verify a receiver before enabling real events.
What if my server was temporarily down? The last delivery status is recorded. Events are notifications — you can always re-fetch the source state via the API.