API & automation
Webhooks
A webhook endpoint receives change events as they happen, signed so you can prove they came from Ranksify. Use them to page someone, open a ticket, or write the event into your own warehouse.
Create an endpoint
- Open Settings. Managing webhooks requires an admin role.
- Add the HTTPS URL that will receive deliveries.
- Choose the event types to subscribe to, or take all of them.
- Copy the signing secret.
The signing secret is shown once, in the creation response. Afterwards the list shows only a mask of the form
whsec_…abcd; the secret itself is stored encrypted at rest. If you lose it, delete the endpoint and create a new one.The URL is validated when you save it, not first discovered to be unreachable at delivery time. Endpoints that resolve to private or internal addresses are refused outright.
Event types
| Event | Fires when |
|---|---|
change_event.visibility_gained | You started appearing where you previously did not. |
change_event.visibility_dropped | You stopped appearing where you previously did. |
change_event.rank_improved | You are being placed higher in answers. |
change_event.rank_dropped | You are being placed lower. |
change_event.citation_gained | New URLs of yours are being cited. |
change_event.citation_lost | Previously cited URLs no longer are. |
change_event.rival_ad_on_branded_prompt | An ad we could tie to a competitor you track — by its destination domain or by the advertiser name shown on it — appeared alongside a prompt that names your brand, on an engine where we can see the ad slots. Neither match proves that competitor bought the ad; the payload carries a link and a name, not a buyer. |
change_event.created | Catch-all. An endpoint carrying it receives every kind above. |
Delivery headers
X-Ranksify-Event— currently alwayschange_event.created, on every delivery. Do not route on it. The specific kind is in the payload body; read that instead.X-Ranksify-Timestamp— Unix seconds.X-Ranksify-Signature—v1=followed by a hex digest.
Verifying the signature
The signature is HMAC-SHA256 over the string {timestamp}.{rawBody}, keyed with your signing secret. Rebuild it from the raw request body — parsing the JSON and re-serialising it will change the bytes and the signature will not match.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, headers, secret) {
const timestamp = headers["x-ranksify-timestamp"];
const received = headers["x-ranksify-signature"];
const digest = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const expected = Buffer.from(`v1=${digest}`);
const actual = Buffer.from(received);
return expected.length === actual.length && timingSafeEqual(expected, actual);
}Two things worth getting right
- Compare in constant time. A plain
===on the digest leaks timing information. - Reject stale timestamps. Check the timestamp is within a window you are comfortable with before you trust the payload, so an old capture cannot be replayed at you.