Skip to main content

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

EventWhen
visibility.check_completedafter a batch visibility check finishes (e.g. nightly monitoring)
content.publishedafter content is published to a CMS (WordPress / Webflow)
alert.triggeredwhen an alert fires (e.g. SoV drop, new competitor)
attribution.order_syncedafter 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 — a sha256=<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.