# Webhooks

> Send an Outscope campaign's new companies or new people to Zapier, Make, n8n, Clay or your own app as they're found, each request signed with HMAC.

Canonical: https://outscope.co/docs/webhooks
Last updated: 2026-11-11

A webhook sends a campaign's new companies, or the people found there, to an address of yours as soon as they're found: a Zapier or Make hook, an n8n workflow, a Clay table, or your own app. Webhooks come with a plan.

## Adding one

1. Open the campaign, then **Export** and **Webhooks**.
2. Choose **New companies** or **New people**, paste the address (it has to start with `https://`), and pick the lowest company score to send.
3. Copy the signing secret it shows. It's shown once.
4. **Send a test** posts a sample, so you can see it arrive and map its fields.

A campaign has up to five webhooks: for example companies scored 8.5 and up to Slack through Zapier, and every person to your CRM.

## When they're sent

- **New companies:** after each morning update, **Find new companies**, or a campaign's first run, for every company that run found at the score and up.
- **New people:** when someone is found at one of the campaign's companies, from **Find people**, the company panel or the morning search.

Companies and people on a do-not-contact list are never sent. Each company or person goes to each webhook once.

## What arrives

A `POST` with a JSON body. Keys are the same as the CSV columns.

```json
{
  "event": "company.found",
  "sent_at": "2026-11-11T09:12:44.512Z",
  "campaign": { "id": "...", "name": "Teams building outbound", "url": "https://outscope.co/dashboard/..." },
  "company": {
    "name": "Acme",
    "domain": "acme.example",
    "score": 9.1,
    "why_now": "Acme is hiring its first two SDRs, so it is starting outbound now.",
    "signals": ["Hiring first or additional SDRs", "New outbound or demand gen leader"],
    "evidence_url": "https://jobs.example.com/acme/sdr",
    "line": "build our outbound motion from scratch",
    "posting": { "title": "Sales Development Representative", "url": "https://jobs.example.com/acme/sdr", "posted_at": "2026-11-09T00:00:00.000Z" },
    "url": "https://outscope.co/dashboard/...?company=..."
  }
}
```

```json
{
  "event": "person.found",
  "sent_at": "2026-11-11T09:14:02.118Z",
  "campaign": { "id": "...", "name": "Teams building outbound", "url": "https://outscope.co/dashboard/..." },
  "person": { "name": "Jo Rivera", "first_name": "Jo", "last_name": "Rivera", "title": "VP Sales", "role": "sales", "email": "jo@acme.example", "phone": null, "linkedin": null, "first_line": null },
  "company": { "name": "Acme", "domain": "acme.example", "score": 9.1, "why_now": "...", "url": "https://outscope.co/dashboard/...?company=..." }
}
```

`phone` is there when you asked Find people for mobiles and one was found. The first line is written a few seconds after someone is found, so it's often still empty here; it's in the People tab and the exports.

## Checking the signature

Every request carries `Outscope-Signature: t=<unix seconds>,v1=<hex>`. The hex is an HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with your secret. Recompute it, compare, and refuse anything older than five minutes:

```js
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, rawBody, header) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(v1, "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}
```

Use the body exactly as it arrived, before parsing it. Each request also carries `Outscope-Event` and `Outscope-Delivery`, an id that stays the same if a request is sent again.

## When your address doesn't answer

Answer with any 2xx within ten seconds. Anything else is tried four more times over about fifteen minutes, then given up. The Webhooks dialog shows each address's last answer and how many were sent, failed or are waiting.

