Loading...

Webhooks

A URL of yours that we POST a small JSON message to the moment something changes — so your code never asks on a timer.

  • A webhook is a subscription: one URL plus the events you want there. Add a record to products, we POST to https://example.com/hooks/searchstack.
  • Every active subscription for that event type gets its own signed message. What you didn’t tick is never sent.
  • Your app reacts at once — clear a cache, start a release, post to Slack.
1

What a webhook is

A webhook is a phone call your app gets when something changes. Instead of polling to ask “anything new?”, Search Stack POSTs a small JSON message to a URL you own the moment an event happens — a record added, a list deleted, an eval regressed. Webhooks live under Developers → Webhooks.

The Webhooks page
2

Pick a URL and the events

Give it the URL that should receive the calls, then pick which events you care about. The catalog is grouped by area — API keys, contributors, evals, lists, media, search results and more — so you subscribe to exactly what your app needs and nothing else.

The create-webhook dialog
3

Signed, retried, de-duplicable

Chosen events show as chips. Click Create and Search Stack shows the signing secret once — copy it now. Every delivery is signed with it (HMAC-SHA256) so your endpoint can verify the call really came from us. Deliveries retry on failure and each carries a unique id you can use to de-duplicate.

Selected event chips and the signing secret

Auto-plays · use Back / Next to step through at your own pace.

Create a webhook
  1. Developers → WebhooksCreate Webhook.
  2. The URL: an HTTPS address on your side, reachable from the internet.
  3. The events, from the grouped list.
  4. Create. The signing secret shows once — copy it now. Rotate secret issues a new one.
The Create Webhook dialog with a URL filled in and the grouped event catalog open
The events you can subscribe to
GroupEventsFires when…
Search resultssearchresult.created, .updated, .deleted, .bulk-createda record is added, changed or removed. Bulk adds send one summary event with a count.
Listslist.created, .updated, .deleteda list is created, its settings change, or it is deleted.
Fieldsfacet.*, searchable-field.*, resource.*a field is added, renamed or removed on a list. Renames carry previous_name.
Mediamedia.uploaded, media.deleted, media-store.created, media-store.deleteda file or media store changes. Store credentials are never included.
Accessapi-key.created, api-key.deleted, contributor.added, contributor.removedan API key or contributor is added or removed. The key value is never included.
Evalseval.run-completed, eval.run-regressedan evaluation finishes, or its score drops below the baseline.
Accountsubscription.auto-upgraded, webhook.pinga plan is auto-upgraded near a limit (carries from_plan/to_plan), or you send a test ping.
What a delivery looks like

The body is the event’s data as JSON, sent as application/json:

{
  "id": "tt1375666",
  "list": "movies",
  "data": { "name": "Inception", "year": 2010 }
}
HeaderWhat it is
X-Searchability-EventThe event type, e.g. searchresult.created.
X-Searchability-DeliveryA unique id for this delivery: store it, ignore repeats.
X-Searchability-TimestampWhen it was signed, in Unix epoch seconds.
X-Searchability-Signaturesha256=<hex>: an HMAC of the timestamp, the body and your secret.
Verify the signature

Your URL is public, so anyone could POST to it. The signature is HMAC-SHA256 of timestamp + "." + body using your subscription’s secret, hex-encoded, prefixed sha256=.

  • Recompute it and compare in constant time, as below.
  • Reject a timestamp more than a few minutes off your clock, so old messages cannot be re-sent.
public static bool IsValid(string secretBase64, string timestamp, string payload, string signatureHeader)
{
    const string Prefix = "sha256=";
    if (!signatureHeader.StartsWith(Prefix, StringComparison.Ordinal)) return false;

    var key = Convert.FromBase64String(secretBase64);
    var data = Encoding.UTF8.GetBytes(timestamp + "." + payload);

    using var hmac = new HMACSHA256(key);
    var expected = Convert.ToHexString(hmac.ComputeHash(data)).ToLowerInvariant();
    var actual = signatureHeader[Prefix.Length..];

    return CryptographicOperations.FixedTimeEquals(
        Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(actual));
}
const crypto = require('node:crypto');

function isValid(secretBase64, timestamp, payload, signatureHeader) {
  const prefix = 'sha256=';
  if (!signatureHeader.startsWith(prefix)) return false;

  const key = Buffer.from(secretBase64, 'base64');
  const expected = crypto.createHmac('sha256', key)
    .update(`${timestamp}.${payload}`).digest('hex');

  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(signatureHeader.slice(prefix.length), 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Retries and failures
  • Sent in the background. A temporary failure — 5xx, timeout, dropped connection — is retried a few times with a growing wait.
  • A URL that keeps failing is retried from the queue several more times, then set aside to be looked at rather than dropped.
  • Respond fast with a 2xx, then do the slow work — a slow answer looks like a failure and gets retried.
  • Expect duplicates. Keep the X-Searchability-Delivery ids you have seen and ignore repeats.
  • Send test ping sends a webhook.ping any time — checks URL, signature and 2xx in one go.
Manage subscriptions from the API

Subscribe / list / edit / delete / test are also on the public API and the MCP tools, for a key with webhook:* permissions:

  • GET /webhook/events — the event types you can subscribe to.
  • POST /webhook — create one (url + events); the response carries the secret once.
  • PUT / DELETE /webhook/{account}/{id} — change the URL or events, or remove it.
  • POST /webhook/{account}/{id}/test — send a test ping.
  • Clients: client.Webhooks.…. Shapes in the API reference.
  • Rotating or viewing the secret and reading delivery logs are console-only, so a leaked API key can never read your signing secret.
Top