baba/apiOpenAPI schema

Documentation

View .md

Webhooks

Rather than polling for a story that breaks or a gap that opens, have us post it to you. Webhooks are an Enterprise scope.

Setting one up

In the console, give a public HTTPS URL, the topics you care about (leave it empty for all of them), and whether you want breaking, blindspot, or both. You get a signing secret once, at creation.

The destination must be a public HTTPS hostname on port 443. Private addresses, link-local ranges, credentials in the URL, fragments and non-standard ports are rejected when you save, and the hostname is resolved again before every delivery, so a domain that later points inside a private network stops being delivered to.

Five enabled endpoints are included. Disable one before adding a sixth.

The delivery

Delivery
POST /your/receiver HTTP/1.1
Content-Type: application/json
User-Agent: baba-News-Webhooks/1.0
X-Baba-Event-Id: 3a1e6c88-5b1d-4a2f-9d43-0b7c6e0a11f2
X-Baba-Signature: t=1788350400,v1=4c1b...9ea3

{
  "id": "3a1e6c88-5b1d-4a2f-9d43-0b7c6e0a11f2",
  "type": "story.blindspot",
  "storyId": "44",
  "state": "blindspot",
  "topic": "politics",
  "headline": "Cabinet approves the deal",
  "url": "https://news.itsbaba.com/812/cabinet-approves-the-deal",
  "observedAt": "2026-09-01T08:14:22.000Z"
}

type is story.breaking or story.blindspot. storyId is the cluster id, so the story detail endpoint gives you the full record.

Verifying the signature

X-Baba-Signature is t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256(secret, "<t>.<raw body>"). Verify against the raw request bytes, before any JSON parsing, and compare in constant time. Reject a timestamp more than five minutes from now.

Verification
import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody is the exact request bytes, before JSON parsing.
function verifyBabaWebhook(rawBody, header, secret) {
  const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header ?? '');
  if (!match || Math.abs(Date.now() / 1000 - Number(match[1])) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(match[1] + '.').update(rawBody).digest();
  return timingSafeEqual(expected, Buffer.from(match[2], 'hex'));
}
// After verification: persist event.id with a unique constraint, enqueue work,
// and return 2xx. A duplicate id must return 2xx without repeating the action.

Retries

Answer 2xx within ten seconds. Anything else, including a timeout, is retried on a fixed ladder.

AttemptWhen
1Immediately
21 minute later
35 minutes later
415 minutes later
51 hour later
64 hours later

A 4xx other than 408 or 429 stops the ladder at once: it says the request itself is wrong, and repeating it will not help. After six attempts the delivery is marked failed and is not retried again.

Delivery is at-least-once. Make your handler idempotent on id.

Redirects are not followed. Point the endpoint at the final URL.