Docs · Pro
API and webhooks
Manage watches and read results programmatically, and receive every match as a signed webhook.
Authentication
Create a token in Settings → API and send it as a bearer token. Tokens belong to a workspace; the API is available on the Pro plan. Each token can make 60 requests per minute; above that you get 429 with a Retry-After header.
curl https://trykeywordsonar.com/api/v1/watches \ -H "Authorization: Bearer ks_…"
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /watches | List watches. |
POST | /watches | Create a watch. Body: name, query, type, sources, communities_include, communities_exclude, domains_include, domains_exclude, item_types, fields, languages, excluded_authors, nsfw, rule_enabled, rule_text, destinations[{destination_id, cadence}]. |
GET | /watches/:id | Get a watch. |
PATCH | /watches/:id | Update any of the fields above. |
DELETE | /watches/:id | Delete a watch and its results. |
POST | /watches/:id/pause | Pause a watch. |
POST | /watches/:id/resume | Resume a watch (subject to plan limits). |
GET | /results | List results. Filters: watch, source, type (post|comment), state (unread|read|done|hidden|filtered|all), from, to (YYYY-MM-DD), q, limit, cursor. |
GET | /destinations | List destinations. |
Lists return { "data": [...] }; /results also returns next_cursor. Errors return { "error": { "status", "message" } }.
Webhooks
Add a webhook destination and attach it to a watch. On Immediate we send one request per result; on other cadences, one batch per cadence. Failed deliveries are retried with back-off for 24 hours, and the last delivery status is shown on the Destinations page.
POST https://your-endpoint.example/keyword-sonar
x-keywordsonar-event: result
x-keywordsonar-signature: t=1790180000,v1=5d0c…
{
"type": "result",
"delivered_at": "2026-09-23T14:02:11.000Z",
"results": [{
"result_id": "r_…", "watch_id": "w_…", "watch_name": "Fernly",
"source": "reddit", "community": "r/SaaS", "item_type": "comment",
"title": "What are you using for invoicing?", "snippet": "…moved our billing to Fernly…",
"permalink": "https://www.reddit.com/r/SaaS/comments/…",
"author": "maya_builds",
"published_at": "2026-09-23T14:01:40.000Z", "collected_at": "2026-09-23T14:01:52.000Z",
"matched_terms": ["fernly"]
}]
}Verifying signatures
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}