Skip to main content
Browse documentation

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

  1. Open Settings. Managing webhooks requires an admin role.
  2. Add the HTTPS URL that will receive deliveries.
  3. Choose the event types to subscribe to, or take all of them.
  4. 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

EventFires when
change_event.visibility_gainedYou started appearing where you previously did not.
change_event.visibility_droppedYou stopped appearing where you previously did.
change_event.rank_improvedYou are being placed higher in answers.
change_event.rank_droppedYou are being placed lower.
change_event.citation_gainedNew URLs of yours are being cited.
change_event.citation_lostPreviously cited URLs no longer are.
change_event.rival_ad_on_branded_promptAn 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.createdCatch-all. An endpoint carrying it receives every kind above.

Delivery headers

  • X-Ranksify-Event — currently always change_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-Signaturev1= 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.