Skip to main content
When an alert matches, we POST to your webhook_url. Deliveries are signed, at-least-once, and never sent for an empty window.

Headers

Payload

Event rows use the same shape as the Signals API, so one parser handles both. Switch alerts carry the switch row shape instead — from, to, category, churned_at, adopted_at, gap_days. The query field is a ready-made API call that reproduces or widens the alert.
landed_at appears on webhook events only. It is when the transition was written to our store — the axis the watermark moves on. event_at remains the scan time.

Verifying the signature

Sign the raw request body. Re-serializing parsed JSON changes key order and whitespace, and the signature will never match.

Delivery guarantees

  • At-least-once. A failed delivery keeps the watermark in place and the window is re-sent. Dedupe on X-TC-Delivery-Id.
  • Truncation is pagination. truncated: true means more events exist; they arrive on the next sweep. Each delivery carries at most 100 events.
  • Landing order, not event order. Sort on event_at yourself if you need chronology.
  • No POST on empty. Quiet windows advance the watermark silently.
  • Auto-disable. Ten consecutive failures set the alert to disabled.
  • HTTPS only. Private, internal and cloud metadata hosts are rejected at creation, and redirects are never followed.

Delivery frequency

delivery_frequency controls how often an alert may fire, independently of how fresh the underlying data is.
Keep busy alerts on hourly. A daily alert whose scope produces more than 100 events a day can only ever drain 100 a day, so its backlog grows.

Frequently asked questions

Delivery is at-least-once. If your endpoint fails or times out, the watermark stays in place and the next sweep re-delivers the same window — that is the design that guarantees nothing is lost. Deduping on X-TC-Delivery-Id is what turns at-least-once into effectively-once on your side.
The watermark moves on landing time — when a transition was written to our store — not on the scan time in event_at. That is deliberate: under crawler lag a row can land days after its scan, and an event-time watermark silently dropped those. On landing time a late row simply falls in the next window, so it is delivered late rather than lost. The consequence is that a payload can contain an event whose event_at is older than ones you already received. Sort on event_at yourself if you need chronology.
No. Quiet windows advance the watermark silently. Your endpoint only hears from us when there are events to deliver.
Any 2xx. The watermark advances only on a 2xx response, so returning quickly and acknowledging before you do heavy processing is the right pattern. Ten consecutive failures disable the alert.
Compute HMAC-SHA256(webhook_secret, timestamp + "." + rawBody) and compare it to the X-TC-Signature header in constant time. Sign the raw request body, never a re-serialization of the parsed JSON — key order and whitespace will differ and the comparison will fail. Also reject stale timestamps, for example more than 5 minutes of skew, for replay protection.
Every event carries detection_url, the URL the crawler actually loaded for that row. An event on docs.example.com is not an event on the apex domain. Show detection_url to your users, not just domain.