Tracker
Watch companies and people for the changes that matter — 55 rule types, refresh cadences from daily to every six months, signals delivered by webhook and API.
We built Tracker to watch companies and people on LinkedIn so you don't have to. Add entities to a list, pick rules ("headcount grew 20%", "person changed company", "new funding round"), and we re-check everything on your schedule. When a rule matches, you get a signal: pushed by webhook and stored for the API. You declare what matters; the watching, comparing, and noise-filtering happen for you.
Core concepts
| Concept | What it is |
|---|---|
| List | A named set of entities sharing rules and a refresh cadence — company list or person list, never mixed |
| Dynamic list | A list backed by a saved search: membership refreshes itself, and a member that stops matching emits a departure signal |
| Entity | One company or person, tracked with its own history |
| Rule | A condition checked on every refresh ("headcount crossed 100"). Up to 25 per list |
| Signal | A detected change that matched a rule — delivered by webhook and stored for the API |
| Refresh interval | How often a list's entities are re-checked: 1–180 days (required when you create the list) |
| Lookback | Optional per-rule window (1–90 days) — compare against ~N days ago instead of the last check |
A rule fires only when tracked data actually changes in a way that matches; a refresh that finds nothing produces nothing.
How monitoring behaves
- The first check is a baseline. When you add an entity, its first check snapshots the current state and fires nothing (there's nothing to compare against yet). Most rules can fire from the second check onward, about one refresh interval after the baseline: a daily list added today can fire its first signals tomorrow, a weekly list about a week in. Some rules wait one more check; see when a rule can first fire. A fresh list with no immediate signals is behaving correctly. (Want history on day one? See day-one signals.)
- The baseline check is billed. Adding an entity is free, but its first check is a normal check at your list's per-entity rate, so loading 1,000 people onto a standard-rate list costs 2,000 credits before the first scheduled run.
- Every check runs on fresh data. What a check compares against is never older than your list's refresh interval — a monthly list is never diffing stale leftovers. If a check can't complete, we retry that entity on the next cycle; an incomplete check never turns into a false "change".
- The list sets the cadence. A scheduled run checks every active entity
in the list, and runs land overnight (US-Pacific), so mornings show fresh
data. New entities are usually baselined within about an hour of adding,
without delaying the entities already tracked. The exception is when the
list's next scheduled run is less than half a refresh interval away: new
entities then wait and are baselined in that run. On a 30-day list, anyone
added in the 15 days before a run is baselined by the run itself.
The company and person list endpoints return
nextRefreshAt, so you always know when the next scheduled run is. Adding or removing entities never moves it. - You can refresh on demand. Refresh a company or person tracker list immediately, at most once every 12 hours per list. Check the tracker overview first to see what the run will cost.
- You can move a scheduled run. Reschedule a list's next refresh to any date from tomorrow up to 90 days out (free); later refreshes continue at the list's interval, counted from the rescheduled run. Use it to skip a cycle or line runs up with your billing period.
- Run heads-ups are available on request. We can tell you about a day
before a scheduled run and again when it completes
(
tracker.list_run_upcomingandtracker.list_run_completedat Webhooks, plus in-app notifications). Ask us to turn them on for your organization.
| When | What happens |
|---|---|
| Entity first added | Baseline captured, usually within about an hour, billed as a check. No signal. |
| Second refresh onward | Compared against the previous state; matches fire signals (some rules from the third check). |
| Entity paused / removed | No further checks or charges; signal history is kept. |
When a rule can first fire
Every rule needs a before and an after, so nothing fires on the baseline check. From there, rules fall into two groups:
| Earliest signal | Rules |
|---|---|
| Second check (one interval after the baseline) | person_changed_company, person_got_promoted, person_got_demoted, headcount and growth rules, funding rounds, job postings, news, posts, location and status changes, and every other rule not listed below |
| Third check (two intervals after the baseline) | person_title_changed, person_started_company, person_added_role, person_employment_type_changed, person_skills_added, person_new_certification, person_tag_gained, person_summary_changed, person_became_verified, person_became_premium, person_became_influencer, person_became_top_voice, technology_added, company_name_changed, company_description_changed, company_went_inactive, acquired_company, new_investor, joined_accelerator |
The third-check rules skip the first comparison after the baseline: that
comparison is a settling pass for a newly added entity, not a change. On a
30-day list, person_changed_company can fire about 30 days after you add
someone and person_title_changed about 60 days after. Three status rules
sit in between: person_open_to_work, person_is_hiring, and
person_stealth_changed report a status switching off from the second
check, but a status switching on only from the third. Want
acquired_company, new_investor, or joined_accelerator on day one? All
three support day-one signals.
Day-one signals (initial look-back)
A brand-new list doesn't have to start silent. Pass initialSignals when
you add entities (or save rules) and we look backward (up to 30 days,
defaulting to your refresh interval) and fire signals for recent events that
would have matched: funding rounds,
matching job postings, news, posts, acquisitions, new investors, layoffs.
For joined_accelerator, day-one signals cover every accelerator membership
the company already holds, whatever its age — see
Accelerators.
Set lookbackPeriodDays explicitly: the default is your refresh interval, so
a daily list only looks back one day unless you say otherwise.
Look-back works for rules anchored to dated events, plus accelerator membership — 16 of the 55 types:
new_funding_round, funding_stage_changed, job_posting_with_keyword,
job_posting_in_function, news_with_keyword, company_news,
company_posted, company_posted_with_keyword, acquired_company,
new_investor, joined_accelerator, recent_layoffs, person_posted,
person_posted_with_keyword, person_reacted_to_post,
person_commented_on_post
Diff-based rules (headcount thresholds, title changes, profile edits) need a
before and an after, so they start from the second check as usual —
unsupported rules come back listed in skippedRules, nothing fails.
The tracker rule catalog marks every rule with supportsInitialSignals so
you can check programmatically.
Day-one signals are billed per entity, only on success, and they seed the baseline — the first scheduled refresh won't re-fire the same events.
Adding entities: identifiers
Send at least one identifier per entity — any of these works:
| Entity | Identifiers |
|---|---|
| Person | linkedinUrl (linkedin.com/in/...), linkedinSlug, linkedinUserId (numeric) |
| Company | linkedinUrl, linkedinSlug, linkedinOrgId (numeric), domain |
Prefer the numeric ID when you have it: it stays stable even when a
person changes their vanity URL, so it's the safest key for long-lived lists.
A slug we can't resolve right away is still accepted: the entity is created
as pending and resolved automatically, so a bulk upload never fails on
individual rows.
Legacy /pub/... LinkedIn URLs (the pre-2013 format) aren't parsed; convert
them to the /in/ form, or send the slug or numeric ID directly.
Limits
| Limit | Value |
|---|---|
| Companies per list | 100,000 |
| People per list | 100,000 |
| Tracked entities per organization | 100,000 |
| Members per dynamic list | 10,000 |
| Rules per list | 25 |
| Entities per add/remove call | 10,000 |
| Refresh interval | 1–180 days |
| Lookback period | 1–90 days |
| Reschedule next run | Tomorrow to 90 days ahead |
| API requests | 120 / minute (reads and writes) |
Billing
-
2 credits per entity per refresh at the standard rate. 500 companies checked weekly ≈ 1,000 credits/week. See Billing & credits.
-
Volume and frequency earn automatic discounts:
Rate Credits / entity / refresh When it applies Standard 2 default Scale 1 10,000+ tracked entities or a daily interval Bulk 0.5 10,000+ tracked entities and a daily interval Tiers apply on their own, with no plan change needed. On-demand refreshes bill at the Standard or Scale rate, never Bulk, and don't move the next scheduled run.
-
Premium rules set a floor. Nine premium rules watch news coverage and LinkedIn activity on every refresh, so a list containing at least one bills at minimum the Scale rate (1 credit / entity / refresh) — the Bulk discount never drops below it. Standard-rate lists are unaffected, and the floor applies once per list no matter how many premium rules it carries.
-
Free: adding entities, reading signals, managing lists, dummy signals, payload previews. Two exceptions: the baseline check an entity gets after you add it is a normal check at your list's rate, and opting into day-one signals bills per entity, on success.
-
Charged per refresh, never per signal: a refresh firing five signals costs the same as one firing none.
-
Out of credits? Nothing breaks. If your balance can't cover a run, we skip it before charging anything: no partial runs, no backlog of surprise charges. The list retries daily instead of waiting out its full interval, and tracking resumes on its own once you top up.
-
Forecast spend with the tracker overview (
GET /v1/tracker/overview): every list, its rules, an upcoming-refresh timeline, and estimated daily/monthly credit burn. -
Pause a list (
isActive: false) to stop charges without losing anything.
Credit amounts are defaults — custom pricing is available for bulk use cases.
Rule mechanics
- Per-item rules emit one signal per detected item (posts, news, job postings, funding rounds), so webhook volume can fan out. Aggregated rules (headcount, title changes) emit one signal per entity per refresh.
- Updating rules — two modes, never mixed: pass
trackingRulesto replace the whole set, oraddRules/removeRuleIdsfor granular edits. Combining them in one request returns a400; the guard exists so a small edit can't accidentally wipe your rule set. - Rule identity survives edits. Saving a list keeps the same
ruleIdfor rules you didn't change, so signal history and integrations keyed onruleIdstay intact.
Company rules
| Group | Rules | Tune with… |
|---|---|---|
| Headcount & growth | headcount_crossed_threshold, headcount_growth_percent, employee_count_milestone, department_size_threshold, recently_hired_with_title, follower_count_growth | thresholds, % change, direction, departments, title keywords |
| Funding & financials | new_funding_round, funding_stage_changed, acquired_company, new_investor, joined_accelerator | min amount, round/stage types, investor types, accelerators |
| Hiring & postings | job_posting_with_keyword, job_posting_in_function | keywords, seniority, work modality, min postings, functions |
| News & content | company_news, news_with_keyword, company_posted, company_posted_with_keyword | keywords, min articles/reactions |
| Profile changes | hq_location_changed, company_status_changed, technology_added, company_name_changed, company_description_changed, company_logo_changed (temporarily paused), company_went_inactive, new_office_location, recent_layoffs | countries/cities, target statuses, technologies, keywords |
Example:
{
"type": "new_funding_round",
"entityType": "company",
"minAmountUsd": 10000000,
"roundTypes": ["series_b", "series_c"]
}Person rules
| Group | Rules |
|---|---|
| Career changes | person_changed_company, person_title_changed, person_got_promoted, person_got_demoted, person_started_company, person_employment_type_changed, person_stuck_in_role, person_tenure_milestone, person_added_role |
| Status signals | person_stealth_changed, person_open_to_work, person_is_hiring, person_became_verified, person_became_premium, person_became_influencer, person_became_top_voice |
| Profile changes | person_headline_changed, person_summary_changed, person_location_changed |
| Skills & activity | person_skills_added, person_new_certification, person_tag_gained, person_posted, person_posted_with_keyword, person_reacted_to_post, person_commented_on_post |
| Growth | person_connections_milestone, person_follower_milestone |
| Contact | person_contact_change |
That's the full catalog: 26 company rules and 29 person rules.
company_logo_changed is temporarily paused and won't fire until we
re-enable it; you can keep it on a list in the meantime. Direction
rules take direction (entered/exited, started/stopped, or
either); keyword rules take match lists. The full field schema for every
rule is in the API reference; the tracker rule catalog
(GET /v1/tracker/rules) returns it programmatically.
Stealth moves are trackable. person_stealth_changed fires when a
watched person enters or exits stealth mode; see
Stealth Tracking for why that signal matters.
Premium rules
Nine rules watch more than the base profile: on every refresh we also monitor news coverage and LinkedIn activity for each tracked entity:
| Watches | Rules |
|---|---|
| News coverage (company) | company_news, news_with_keyword |
| LinkedIn posts (company) | company_posted, company_posted_with_keyword |
| LinkedIn activity (person) | person_posted, person_posted_with_keyword, person_reacted_to_post, person_commented_on_post |
| Contact details (person) | person_contact_change |
- A list containing at least one premium rule bills at minimum the Scale rate; see Billing. The floor applies once per list: one premium rule or eight, same per-entity rate.
- Mix premium and standard rules freely within the 25-rules-per-list limit.
- Spot them programmatically: the tracker rule catalog
(
GET /v1/tracker/rules) returnsisPremiumfor every rule type, and the tracker overview's estimates already reflect the floor, so you see the true per-refresh cost before a premium rule ever runs.
Recipes
- Buying intent (companies):
job_posting_with_keyword("data engineer")headcount_growth_percent(15%+) +new_funding_round($5M+) — any one means spending; all three together is a very strong signal.
- Champion tracking (people):
person_changed_company+person_got_promoted+person_open_to_work— a champion who moves companies is a warm intro to their new org. - Fundraise timing (companies):
headcount_growth_percent(30% over 90 days vialookbackDays) +job_posting_in_function(Finance, Legal) +recently_hired_with_title(CFO, VP Finance).
Signals & noise
Tracker is conservative by design. It alerts on real changes and stays quiet otherwise:
- Tiny fluctuations don't fire. A one-person headcount wobble or a few-follower dip never triggers a rule; a change that crosses a threshold you set always counts.
- The same signal doesn't fire twice. Within a rule's lookback window we suppress repeats, and a status that flips on and off ("open to work" toggled back and forth) won't flood you with alerts.
- Missing data isn't a change. A temporarily unavailable field is treated as unknown, never as "went to zero" — no false "person left their company" alerts.
- A second job isn't a job change. Taking on an additional role without
leaving the current one doesn't count as changing company —
person_changed_companyfires on actual moves. - Cosmetic title edits aren't promotions. Only real moves up or down fire promotion and demotion signals.
- Every signal carries a
confidencescore (0–1). Deterministic rules report1.0; interpretive ones report lower scores so you can weight them in your automations.
Consuming signals
Webhooks (real-time). A firing rule delivers a tracker.signal_detected
webhook: a self-contained envelope with the entity, the matched rule, a
human-readable summary, structured changeData, source URLs (sources),
a methodology note, and confidence:
{
"success": true,
"eventId": "evt_...",
"occurredAt": "2026-06-02T12:00:00.000Z",
"entity": {
"type": "company",
"identifiers": { "linkedinSlug": "example-co", "domain": "example.com", "name": "Example Co" }
},
"subscription": {
"id": "sub_...",
"type": "headcount_crossed_threshold",
"listId": "list_...",
"listName": "Target Accounts"
},
"signal": {
"type": "headcount_crossed_threshold",
"summary": "Headcount crossed 100 (now 104)",
"changeData": { "...": "rule-specific diff — see below" },
"sources": [],
"methodology": "...",
"confidence": 1.0,
"isDummy": false
},
"billing": { "creditsCharged": 2, "billingMethod": "per_check" }
}eventId is stable across retries; dedupe on it. billing shows what the
check that produced the signal cost; signals themselves are never billed
separately. Configure the endpoint at
fiber.ai/app/webhooks; see
Webhooks for signatures and retries.
What's inside changeData
The envelope is identical for every rule: what varies is changeData, the
structured diff. Preview a signal payload
(POST /v1/tracker/rules/preview-signal) returns the exact shape for any
rule, free; hit it before you write a parser, not after.
The rule integrators ask about most is person_changed_company, so here's
its changeData (placeholder values):
[
{
"companyName": "NewCo",
"linkedinCompanyId": "11111111",
"linkedinCompanySlug": "newco",
"companyLinkedinUrl": "https://www.linkedin.com/company/newco",
"companyDomains": ["newco.example.com"],
"crunchbaseSlug": "newco",
"title": "VP Engineering",
"isCurrent": true,
"startDate": "2026-01-01T00:00:00.000Z",
"endDate": null,
"location": "City, Country",
"employmentType": "Full-time",
"seniority": "Executive"
},
{
"companyName": "OldCo",
"linkedinCompanyId": "22222222",
"linkedinCompanySlug": "oldco",
"companyLinkedinUrl": "https://www.linkedin.com/company/oldco",
"companyDomains": ["oldco.example.com"],
"crunchbaseSlug": null,
"title": "Director of Engineering",
"isCurrent": true,
"startDate": "2023-01-01T00:00:00.000Z",
"endDate": null,
"location": "City, Country",
"employmentType": "Full-time",
"seniority": "Director"
}
]Reading it:
- Both sides of the move are included — the new position(s) first, then
the role that was left. Use array order to tell them apart, not
isCurrent(which reflects each role's state as last seen). - Identifier fields are populated when available and
nullotherwise — never guessed. Match onlinkedinCompanyId,linkedinCompanySlug,companyLinkedinUrl,companyDomains, orcrunchbaseSlug, and build your matcher as a ladder with fallbacks rather than requiring any single field.crunchbaseSlugis the employer's Crunchbase permalink when we know it, so systems keyed on Crunchbase can update straight from the signal without a second lookup. Company signals carry the tracked company'scrunchbaseSlugin the entity identifiers the same way. - Dates are ISO strings and nullable —
endDateon the old role fills in once the change is publicly reflected.
API polling (the reliable fallback). Every signal is stored whether or not
a webhook exists. Read them from the tracker signal feed
(GET /v1/tracker/signals/{listId}) using since (ISO timestamp), cursor,
pageSize (1–100, default 50), and filter: real (default), dummy for
test signals only, or all. Reading signals is free. The older per-list
changes endpoint (GET /v1/tracker/changes/{listId}) is deprecated; move to
the signal feed. A robust
integration does both: webhooks to react now, polling with since to backfill
anything missed.
Signals stay readable through the API indefinitely. A signal is the record:
its changeData and the entity's identifiers. There is no separate
before-and-after snapshot to fetch later, so store what you need from the
signal when it arrives, and use a profile lookup when you want the full
current profile.
Testing tools (both free):
- Fire test signals (
POST /v1/tracker/fire-dummy/{listId}): mark rulesisDummy: trueand fire synthetic signals through your real webhook, flaggedisDummy: true, as often as you like. On an empty list we add a well-known example (Google for company lists, Bill Gates for person lists) so you can test before loading real entities. - Preview a signal payload (
POST /v1/tracker/rules/preview-signal): send a rule, get an example payload. Nothing is created, nothing is delivered.
Managing lists
- The tracker overview (
GET /v1/tracker/overview) shows everything at a glance and answers "how many lists do I have, and how much am I tracking?" Its summary returnstotalCompanyLists,totalPersonLists,totalCompaniesTracked,totalPeopleTracked,totalActiveRules, and estimated daily and monthly credits. ItsupcomingRefresheslists each list's next run (refreshAt), how many entities it will check, and the estimated credits. - Pause/resume:
isActive: falsestops refreshes and charges; rules, interval, and history are preserved. Flip back to resume. - Remove an entity: stops its future checks and charges; signal history is preserved. Re-adding the same entity later does not reset its baseline: its first check back compares against the state we last saw before removal, so anything that changed in between surfaces as signals then. A genuinely new entity starts fresh.
- Delete a list: deactivates the list and everything in it; signal history is retained. A deleted list can't be restored, and adding its entities to a new list baselines (and bills) them again. If you may need the list later, pause it instead.
FAQ
Does a signal include the full profile?
No — a signal carries the rule that fired, a human-readable summary, the
structured diff (changeData), and the entity's identifiers. For job changes
the diff already includes the complete new-position details, which covers
most CRM-update flows. Need the full refreshed profile? Any identifier from
the signal works with our profile-lookup endpoints — that's a separate,
charged call.
I fetch the full profile after a signal. Will it show the change?
Yes. The profile data a tracker check refreshes is the same data our people
search and profile lookups return, and it shows up there within minutes of
the check. Wait a few minutes after the webhook, or confirm the profile's
last_updated_at is at or after the signal's timestamp, and you have the
state the tracker saw. You don't need a live fetch for this; reserve it for
when you need a guaranteed fresh read, since it costs more per profile.
Is there a "left their job" or "retired" signal?
Not currently — person_changed_company fires on moves, and the closest
status signal is person_open_to_work. If a "left, nothing new yet" signal
matters for your use-case, tell us.
Why haven't my lists produced any signals? Work down this list:
- New lists start quiet. Unless you turned on day-one signals, the first signals arrive after a refresh cycle.
- Nothing matched yet. A refresh that finds no matching change fires nothing, and small fluctuations are filtered out (see Signals & noise). Narrow, high-threshold rules on a short list can go quiet for weeks. Fire a test signal to confirm your webhook is wired up.
- Your rules need a third check. Title, role, skills, and most profile-edit rules skip the first comparison after the baseline, so their first signal lands two intervals in; see when a rule can first fire. The whole list is checked together on each run, so signals arrive in bursts with quiet stretches in between.
- You ran out of credits. Runs your balance can't cover are skipped, not queued; tracking resumes once you top up.
- Your plan doesn't include Tracker. Tracker calls return 403 with "Your current plan does not include Tracker access." Contact your account manager to enable it.
- The rule is paused.
company_logo_changedis temporarily paused.
Why did only part of my list refresh today? A scheduled run checks every active entity in the list, so a partial refresh on a non-run day is the hourly pass baselining entities you just added. The tracker overview shows exactly what's due next.
Can I move or skip a scheduled run? Yes. Reschedule the list's next refresh to any date from tomorrow up to 90 days out; later refreshes follow the list's interval from there. To run now instead, refresh on demand (at most once every 12 hours per list).
Does Tracker integrate with Salesforce or HubSpot?
There's no native CRM sync for Tracker. Point a webhook endpoint at your own
handler (or a no-code automation tool) and write each
tracker.signal_detected event into your CRM. A job-change signal already
includes the new company and title, which covers most CRM-update flows. Poll
the signal feed with since to backfill anything a webhook missed.
What happens when a check fails? The entity is retried on the next cycle, and a failed check never fabricates a signal — missing or incomplete data is treated as unknown, not as a change.
API quick reference
All operations are under /v1/; each links to its focused reference page in
the operation index.
| Operation | Method & path |
|---|---|
createTrackerCompanyList | POST /v1/tracker/company-lists |
listTrackerCompanyLists | GET /v1/tracker/company-lists |
getTrackerCompanyList | GET /v1/tracker/company-lists/{listId} |
updateTrackerCompanyList | PATCH /v1/tracker/company-lists/{listId} |
deleteTrackerCompanyList | DELETE /v1/tracker/company-lists/{listId} |
addTrackerCompanies | PUT /v1/tracker/company-lists/{listId}/companies |
removeTrackerCompanies | DELETE /v1/tracker/company-lists/{listId}/companies |
refreshTrackerCompanyList | POST /v1/tracker/company-lists/{listId}/refresh |
getTrackerPersonList | GET /v1/tracker/person-lists/{listId} |
refreshTrackerPersonList | POST /v1/tracker/person-lists/{listId}/refresh |
rescheduleTrackerCompanyList | POST /v1/tracker/company-lists/{listId}/reschedule |
rescheduleTrackerPersonList | POST /v1/tracker/person-lists/{listId}/reschedule |
| Other person-list operations | Same shape under /v1/tracker/person-lists… (…People) |
listTrackerSignals | GET /v1/tracker/signals/{listId} |
listTrackerChanges (deprecated) | GET /v1/tracker/changes/{listId} |
getTrackerOverview | GET /v1/tracker/overview |
listAvailableTrackerRules | GET /v1/tracker/rules |
fireTrackerDummy | POST /v1/tracker/fire-dummy/{listId} |
previewTrackerSignal | POST /v1/tracker/rules/preview-signal |