> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://gtm-tools.sh/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://gtm-tools.sh/_mcp/server.

# Signal Pipeline

> Run detect_signal across a target account list, weight the fired signals, and emit the top-N accounts as a daily digest.

A worked example: run `detect_signal` over a target account list, weight the fired signals, and emit the top-N accounts as a daily Slack digest.

## What this example does

1. Reads a target account list (domains).
2. Runs `detect_signal` for each (free dispatch + 5 credits per fired detector).
3. Scores results using a weighted rubric.
4. Sorts and prints the top accounts.

Cost: \~15 credits per account, so 100 accounts ≈ 1,500 credits (**\$15**).

## Code

```typescript
const KEY = process.env.GTM_TOOLS_API_KEY!;

const WEIGHTS: Record<string, number> = {
  signal_hiring_sales_rep_repost: 5,
  signal_trustpilot_negative_support_reviews: 4,
  signal_hiring_support: 3,
  signal_hiring_sales_leadership: 3,
  signal_socials_spike: 2,
  signal_technologies_identified: 2,
  signal_hiring_sales_rep: 2,
  signal_hiring_role: 1,
  signal_trustpilot_negative_reviews: 1,
};

interface Detection {
  name: string;
  fired: boolean;
  evidence?: unknown[];
}

interface DetectResponse {
  domain: string;
  signals: Detection[];
}

interface Scored {
  domain: string;
  score: number;
  fired: string[];
}

async function detect(domain: string, techs: string[]): Promise<DetectResponse | null> {
  const res = await fetch("https://api.gtm-tools.sh/api/v0/detect_signal", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ domain, techs }),
  });
  if (!res.ok) {
    console.error(`detect_signal ${domain}: ${res.status}`);
    return null;
  }
  return res.json();
}

function score(d: DetectResponse): Scored {
  const fired = d.signals.filter((s) => s.fired).map((s) => s.name);
  const total = fired.reduce((acc, name) => acc + (WEIGHTS[name] ?? 1), 0);
  return { domain: d.domain, score: total, fired };
}

async function run(domains: string[], techs: string[]) {
  const results: Scored[] = [];
  for (let i = 0; i < domains.length; i += 5) {
    const slice = domains.slice(i, i + 5);
    const detected = await Promise.all(slice.map((d) => detect(d, techs)));
    for (const d of detected) {
      if (d) results.push(score(d));
    }
    console.log(`scored ${results.length}/${domains.length}`);
  }
  return results.sort((a, b) => b.score - a.score);
}

const TARGETS = [
  "gymshark.com",
  "mammaly.de",
  "kettleandfire.com",
  "spanx.com",
  "siena.cx",
];

const ranked = await run(TARGETS, ["zendesk.com", "intercom.com"]);

console.log("\n=== Top targets ===");
for (const r of ranked.slice(0, 10)) {
  if (r.score === 0) break;
  console.log(`${r.score.toString().padStart(3)} ${r.domain}  [${r.fired.join(", ")}]`);
}
```

## Sample output

```
scored 5/5

=== Top targets ===
  9 gymshark.com  [signal_hiring_sales_rep_repost, signal_trustpilot_negative_support_reviews]
  4 mammaly.de  [signal_trustpilot_negative_support_reviews]
  3 kettleandfire.com  [signal_hiring_support]
  2 spanx.com  [signal_socials_spike]
```

## Tuning the rubric

The weights are arbitrary; calibrate them to your motion:

* **Churn-prevention pitch**: Weight `signal_trustpilot_negative_support_reviews` highest.
* **Greenfield SaaS sales**: Weight `signal_hiring_sales_rep_repost` and `signal_hiring_sales_leadership` highest.
* **DTC marketing tooling**: Weight `signal_socials_spike` highest.

## Slack digest

Pipe the ranked list into a Slack webhook for daily review:

```typescript
async function postDigest(ranked: Scored[]) {
  const top = ranked.filter((r) => r.score > 0).slice(0, 10);
  const text = top
    .map((r) => `*${r.score}* - \`${r.domain}\` - ${r.fired.join(", ")}`)
    .join("\n");

  await fetch(process.env.SLACK_WEBHOOK_URL!, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ text: `*Top GTM signals: ${new Date().toISOString().slice(0, 10)}*\n${text}` }),
  });
}
```

## Credit budget

| Step                                            | Cost            |
| ----------------------------------------------- | --------------- |
| `detect_signal` × 100 (median 3 detectors fire) | \~1,500 credits |
| Slack post                                      | 0               |
| **Daily total**                                 | **\$15**        |

That's **\$450/month** to keep a rolling signal feed over 100 accounts.

## Next steps

#### [Signals Tools](/signals-tools)

Per-detector reference and tuning.

#### [Outbound](/outbound)

Turn the top targets into outreach.