POST to your webhook_url. Deliveries are signed, at-least-once, and never sent for an empty window.
Headers
Payload
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
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: truemeans more events exist; they arrive on the next sweep. Each delivery carries at most 100 events. - Landing order, not event order. Sort on
event_atyourself 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.
Frequently asked questions
Why must I dedupe on the delivery id?
Why must I dedupe on the delivery id?
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.Why are events not in chronological order?
Why are events not in chronological order?
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.Will I get a webhook when nothing happened?
Will I get a webhook when nothing happened?
No. Quiet windows advance the watermark silently. Your endpoint only hears from us when there are events to deliver.
What should my endpoint return?
What should my endpoint return?
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.
How do I verify the signature correctly?
How do I verify the signature correctly?
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.Why does a subdomain event show a different host?
Why does a subdomain event show a different host?
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.