Skip to navigation

Signal Pipeline

Score a target account list with detect_signal and surface the hottest accounts

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

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:

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

StepCost
detect_signal × 100 (median 3 detectors fire)~1,500 credits
Slack post0
Daily total$15

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

Next steps