dist0
API

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

On this page

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

  1. Open Settings in the dashboard.
  2. Under Send new signals to your URL, paste your endpoint (it must start with https://) and click Save.
  3. 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:

HeaderWhat it is
X-Dist0-TimestampUnix seconds the signature covers
X-Dist0-SignatureThe signature, as v0=<hex>
X-Dist0-Signal-Kindpain or competitor
X-Dist0-Delivery-IdA 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.