API
Webhooks
Instead of asking the API for new signals, have dist0 push each one to a URL you choose the moment it's found.
Last updated July 3, 2026
What a webhook does
The read API answers when you ask it. A webhook is the other way around: you give dist0 a URL, and every time a new signal shows up for your project we send it there right away. That's what no-code tools like Zapier, Make, and n8n plug into as a trigger — and you can point it at your own app just as easily.
Webhooks are part of the Pro plan.
Set it up
- Open Settings in the dashboard.
- Under Send new signals to your URL, paste your endpoint (it must start
with
https://) and click Save. - Tick which signals fire it — buyer pains, competitor mentions, or both.
You can turn the URL off at any time without deleting it, and edit the URL or the signal types whenever you want.
What we send
Each new signal is one POST to your URL with a JSON body:
{
"type": "signal.created",
"project_id": "prj_9f2a",
"signal": {
"id": "sig_7Qd1",
"kind": "pain",
"quote": "I keep losing track of which subreddit a lead came from.",
"author": "some_redditor"
},
"post": {
"id": "post_3xB8",
"permalink": "https://reddit.com/r/…",
"title": "How do you track where leads come from?"
}
}kind is pain or competitor. The signal.id and post.id are the same
durable ids the read API uses, so a pushed signal and one you later fetch line
up.
We also set these headers on every send:
| Header | What it is |
|---|---|
X-Dist0-Timestamp | Unix seconds the signature covers |
X-Dist0-Signature | The signature, as v0=<hex> |
X-Dist0-Signal-Kind | pain or competitor |
X-Dist0-Delivery-Id | A unique id for this send |
Check the signature
Every send is signed with your webhook's signing secret so you can be sure it's from dist0 and wasn't changed on the way. The signature is an HMAC-SHA256 over the timestamp and the exact request body:
signature = "v0=" + HMAC_SHA256(signing_secret, "v0:" + timestamp + ":" + raw_body)Compute the same value on your side and compare it to the X-Dist0-Signature
header. Use the raw request body — parsing and re-encoding the JSON first
will change the bytes and break the check.
import crypto from "crypto";
function verify(rawBody, timestamp, signature, signingSecret) {
const expected =
"v0=" +
crypto
.createHmac("sha256", signingSecret)
.update(`v0:${timestamp}:${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}To block replays, also reject a send whose X-Dist0-Timestamp is more than a few
minutes off from your own clock.
Retries and failed sends
Your endpoint should answer with a 2xx status once it has the signal. If it
returns anything else, or doesn't answer in time, dist0 treats the send as failed
and tries again with growing gaps between attempts. After several failed
attempts we stop and mark the send as given up.
Settings → Send new signals to your URL lists your most recent failed sends with the time and the reason, so you can spot a broken endpoint and fix it.
