Documentation

Webhooks

One POST per hit, to an endpoint you register. Suited to putting hits into your own system — a CRM, a Slack app you built, a database.

Register the endpoint, then point an alert at it

Two steps, because a webhook belongs to your team and is reusable across every alert in every project.

curl -X POST -H "Authorization: Bearer $SPUTNIK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "CRM relay", "url": "https://example.com/hooks/sputnik"}' \
  "https://sputnikintelligence.com/api/v1/webhooks"

The response carries the signing secret. Then point an alert at it:

curl -X POST -H "Authorization: Bearer $SPUTNIK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook": "<webhook-slug>"}' \
  "https://sputnikintelligence.com/api/v1/alerts/<alert-slug>/destinations"

Registering an endpoint on its own delivers nothing. It needs an alert pointed at it.

The payload

{
  "event_id": "4821",
  "event_type": "alert.hit",
  "project": { "slug": "...", "name": "Acme" },
  "alert":   { "slug": "...", "name": "Acme brand mentions", "url": "https://..." },
  "matched_searches": ["Brand"],
  "post": {
    "id": 91823,
    "slug": "...",
    "title": "The one where we argue about pricing",
    "url": "https://example.com/episodes/214",
    "published_at": "2026-08-12T09:00:00+00:00",
    "source": "The Example Show",
    "type": "podcast"
  },
  "snippet": "...the matched passage..."
}

Dedupe on event_id. It is the hit id, so a retry delivers the same id and your consumer can make it idempotent.

There is no full text or transcript in here, deliberately. Fetch those from the API with the post slug. It keeps the payload small, and it keeps one way to get content rather than two that can disagree.

Verifying the signature

Signed with the Standard Webhooks scheme, so any library for that verifies it:

webhook-id:        <event id>
webhook-timestamp: <unix seconds>
webhook-signature: v1,<base64 HMAC-SHA256 of "{id}.{timestamp}.{raw body}">

Keyed on the base64-decoded secret with its whsec_ prefix stripped.

Verify against the raw body

Parse the JSON after you verify. Decoding and re-encoding it changes the bytes — key order, whitespace, unicode escaping — and the signature will not match. This is the mistake everyone makes once.

Check the timestamp too, and reject anything older than a few minutes, so a captured request cannot be replayed at you later.

The secret comes back on every read of the webhook, so you can always look it up again. Endpoints that cannot check signatures — Zapier, most no-code tools — can ignore it.

When your endpoint fails

We retry. After repeated failures the webhook deactivates itself rather than hammering a dead endpoint, and deactivated_reason says why.

While that is true, any alert destination pointing at it reports is_delivering: false. That is the field to watch: an alert can look perfectly healthy while its webhook has quietly switched itself off.

Fix the endpoint, then PATCH the webhook with is_active: true. That also clears the failure streak, so it gets a fresh run of attempts rather than deactivating on its very next hiccup.

Deleting

Blocked with a 422 while any alert still delivers there. Cascading would silently mute those alerts, and you would find out by not being told about something. Set is_active: false instead — that is the reversible move.