---
title: "Create a signal"
method: POST
path: "/linkedin/signals"
tags: ["LinkedIn — Signals"]
---

# Create a signal

`POST /linkedin/signals`

## Request

- Base URL: `https://anjal.app`
- URL: `https://anjal.app/linkedin/signals`
- Auth: API key in header `X-API-Key`

Create a real-time intent watcher. We poll LinkedIn on the chosen
cadence using the named session, classify each NEW post against
the keyword, and (if a webhookUrl is set) POST high-intent matches
to your endpoint with an HMAC-SHA256 signature. The lookback window
is fixed at 24 hours — for historical lookups across a wider window
use POST /v1/linkedin/searches instead.

## Request body

- object
  - `name` string, required
  - `kind` 'keyword' | 'engagement' | 'job_change_watchlist': keyword (default) — saved keyword search. engagement — watch a profile/company for engagers; requires `target`. job_change_watchlist — watch a list of profiles for role/company changes; requires `watchlist`.
  - `query` string: Required when kind=keyword.
  - `target` object: Required when kind=engagement.
    - `kind` 'profile' | 'company', required
    - `url` string, uri, required
    - `label` string
  - `watchlist` object[]: Required when kind=job_change_watchlist. Up to 500 entries.
    - `profileUrl` string, uri, required
    - `label` string
  - `sessionId` string, nullable: Omit or send null to auto-distribute polling across every healthy LinkedIn session for this tenant (recommended — multiplies daily throughput by the number of connected accounts and protects each individual account from rate limits). Pass a specific session id to pin all polls for this signal to that account.
  - `cadence` 'daily' | 'every_12h' | 'every_6h' | 'manual'
  - `webhookUrl` string, uri, nullable
  - `filterMinIntent` 'low' | 'medium' | 'high'
  - `intentDescription` string, required: Plain-English firing rule the LLM classifier treats as the authoritative definition of what should fire. Example: "Flag as ready when the author is a founder/ operator complaining about cold email — skip vendors, agencies, and content marketers."

## Response `201`

Created signal. webhookSecret is returned ONCE on creation when
webhookUrl is set — store it now to verify webhook signatures.

- object
  - `signal` LinkedInSignal
    - `id` string, required
    - `kind` 'keyword' | 'engagement' | 'job_change_watchlist', required: Acquisition strategy for this signal: - keyword: poll LinkedIn search for the saved query. - engagement: poll a tracked profile/company's recent posts and fire on engagers. - job_change_watchlist: poll a list of profiles weekly and fire on role/company changes.
    - `name` string, required
    - `query` string, nullable: Required for kind=keyword; null otherwise.
    - `target` object, nullable: Engagement signals only — the resolved actor target.
      - `kind` 'profile' | 'company'
      - `url` string
      - `urn` string: Resolved urn:li:fsd_profile:* or urn:li:fsd_company:*.
      - `label` string, nullable
    - `lastSeenPostUrn` string, nullable: Engagement signals only — watermark of the most recent post processed. We only fire on engagers of posts newer than this.
    - `watchlistCount` integer, nullable: Watchlist signals only — number of profiles being tracked.
    - `sessionId` string, nullable: null = auto-distribute polling across all healthy LinkedIn sessions for this tenant (recommended). Specific id = pin the signal to that account so all polls come from it.
    - `cadence` 'daily' | 'every_12h' | 'every_6h' | 'manual', required
    - `webhookUrl` string, nullable
    - `webhookSecret` string, nullable: Returned only on creation. Verify with HMAC-SHA256.
    - `filterMinIntent` 'low' | 'medium' | 'high', required
    - `intentDescription` string, required: Plain-English firing rule the LLM classifier uses as the authoritative definition of what should fire. Example: "Flag as ready when the author is a founder/operator complaining about cold email — skip vendors, agencies, and content marketers." The keyword is only a coarse pre-filter; this rule decides engage=true/false and the cited reason on every match.
    - `enabled` boolean, required
    - `lastPolledAt` string, date-time, nullable
    - `nextPollAt` string, date-time, nullable
    - `lastError` string, nullable
    - `lastErrorCode` string, nullable
    - `lastErrorAt` string, date-time, nullable
    - `matchesCount` integer
    - `createdAt` string, date-time
    - `updatedAt` string, date-time

## Other responses

- `403`: Signal limit reached for the tenant's tier.
- `404`: LinkedIn session not found or inactive.

---

[API](https://skmtc.dev/anjal/apis/anjal-api.md) · [All operations](https://skmtc.dev/anjal/apis/anjal-api/llms.txt) · [OpenAPI document](https://skmtc.dev/anjal/apis/anjal-api/revisions/a4044778e294?raw)
