> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tamradar.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Payloads

> Webhook delivery model, payload envelope, update types, and testing.

## How TAMradar delivers updates

TAMradar supports two ways to receive radar findings:

* **Webhooks (push)** — TAMradar POSTs events to your `webhook_url` in real-time. Set `webhook_url` when creating a radar to enable this.
* **Polling (pull)** — Fetch findings on demand via `GET /v1/updates`. Works for all radars, including those created without a `webhook_url`.

Both delivery methods use the same payload format documented below.

## Payload Libraries by Category

### Company Payloads

* [Company Mentions](/webhooks/company/mentions)
* [Company Social Posts](/webhooks/company/social-posts)
* [Company Social Posts (CXO)](/webhooks/company/social-posts-cxo)
* [Company Social Engagements](/webhooks/company/social-engagements)
* [Company Job Openings](/webhooks/company/job-openings)
* [Company New Hires](/webhooks/company/new-hires)
* [Company Promotions](/webhooks/company/promotions)
* [Company Reviews](/webhooks/company/reviews)

### Contact Payloads

* [Contact Job Changes](/webhooks/contact/job-changes)
* [Contact Social Posts](/webhooks/contact/social-posts)
* [Contact Social Engagements](/webhooks/contact/social-engagements)

### Industry Payloads

* [Industry Mentions](/webhooks/industry/mentions)
* [Industry Funding Rounds](/webhooks/industry/funding-rounds)
* [Industry New Companies](/webhooks/industry/new-companies)
* [Industry Job Openings](/webhooks/industry/job-openings)

## Setting Up Webhook Delivery (optional)

If you want real-time push delivery, set up an HTTPS endpoint:

1. Create an HTTPS endpoint on your server that can receive POST requests
2. Pass `webhook_url` when creating your radar
3. Ensure your endpoint returns a 2xx status code to acknowledge receipt

> **No webhook?** Skip `webhook_url` entirely to create a poll-only radar. Findings are always available via `GET /v1/updates` regardless.

## Webhook Payload Format

All webhook payloads follow this structure:

```json theme={null}
{
  "update_id": "550e8400-e29b-41d4-a716-446655440000",
  "update_type": "radar_finding",
  "record_id": "c4d5e6f7-a8b9-4c0d-b1e2-f3a4b5c6d7e8",

  "discovered_at": "2024-03-20T12:30:00Z",
  "data": {
    "radar_id": "54e32291-25a4-459f-8bd6-4f79489ea931",
    "radar_type": "company_job_openings",
    "domain": "example.com",
    "custom_fields": {
      "priority": "high",
      "target_account_id": "0011U00000TFV7MQAX"
    }
  },
  "content": {
    // Event-specific data
  }
}
```

### Base Fields

| Field          | Type   | Required | Description                                      |
| -------------- | ------ | -------- | ------------------------------------------------ |
| update\_id     | string | Yes      | Unique identifier for this webhook update        |
| update\_type   | string | Yes      | Type of update (see Update Types below)          |
| record\_id     | string | No       | Unique identifier for the detected record        |
| radar\_type    | string | Yes      | Type of radar that generated the event           |
| discovered\_at | string | Yes      | ISO timestamp (UTC) when the update was detected |

### Update Types

| Update Type      | Description                                                  |
| ---------------- | ------------------------------------------------------------ |
| `radar_finding`  | New data detected by a radar (default)                       |
| `radar_failure`  | Radar setup or billing failure (may include refund metadata) |
| `radar_created`  | Async bulk item was created successfully                     |
| `bulk_completed` | Async bulk request finished processing all items             |

### Per-item bulk webhook events

Before the final `bulk_completed`, each item delivers its own webhook as it finishes:

* **`radar_created`** — item was created successfully. Includes `code: 201`, `message: "Radar created successfully"`, `completed_at`, and a full `data` block matching the single-radar creation response.
* **`radar_failure`** — item failed validation, conflict, or billing. Includes `completed_at`, `code`, `errors[]`, and `message` matching the sync API error shape. Also emits `discovered_at` (same value as `completed_at`) for backwards compatibility — prefer `completed_at` going forward.

`radar_created` example:

```json theme={null}
{
  "update_id": "efbf7c77-c1ae-4a0d-85b6-116674e9e0b1",
  "update_type": "radar_created",
  "record_id": null,
  "code": 201,
  "message": "Radar created successfully",
  "completed_at": "2026-05-25T11:17:20Z",
  "bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
  "item_index": 0,
  "custom_fields": { "crm_account_id": "acc_001" },
  "status": "success",
  "data": {
    "radar_id": "2ffb1a07-ac95-4a6d-bc34-39f479f1c856",
    "radar_type": "company_job_openings",
    "domain": "stripe.com",
    "radar_status": "active",
    "webhook_url": "https://your-domain.com/webhooks/tamradar",
    "created_at": "2026-05-25T11:17:18Z",
    "deactivated_at": null,
    "next_charge_at": "2026-06-25T11:17:18Z",
    "custom_fields": { "crm_account_id": "acc_001" }
  }
}
```

`radar_failure` example:

```json theme={null}
{
  "update_id": "a1b2c3d4-0001-4000-b000-000000000001",
  "update_type": "radar_failure",
  "record_id": null,
  "completed_at": "2026-05-25T11:17:26Z",
  "discovered_at": "2026-05-25T11:17:26Z",
  "bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
  "item_index": 1,
  "custom_fields": {},
  "status": "error",
  "code": 409,
  "message": "Conflict: Radar already exists",
  "errors": [
    {
      "field": "domain",
      "reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0. You can view it using GET /radars/{radar_id}",
      "conflicting_radar_id": "3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0"
    }
  ],
  "refund_amount_usd": 0,
  "error_id": "45ed35e5-986c-4496-afd8-9a84f6baa410",
  "timestamp": "2026-05-25T11:17:26.000Z"
}
```

### Bulk `update_id` semantics

For `bulk_completed` events, two ID levels can appear and they serve different purposes:

* Top-level `update_id`: idempotency key for the whole `bulk_completed` webhook event.
* Per-item `radars[].update_id`: correlation key for each item's earlier `radar_created` or `radar_failure` event.

Store both when you consume bulk webhooks:

* Deduplicate `bulk_completed` retries by top-level `update_id`.
* Correlate each `radars[]` item back to its individual item event with `radars[].update_id`.

`bulk_completed` payloads use a batch envelope with `completed_at`, `summary`, and `radars[]`. Each entry in `radars[]` is the verbatim payload from that item's per-item webhook:

```json theme={null}
{
  "update_id": "9a2f0f77-5c8b-4b3e-b636-3ad539f4de65",
  "update_type": "bulk_completed",
  "bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
  "completed_at": "2026-05-25T11:17:33Z",
  "summary": {
    "total": 3,
    "created": 2,
    "failed": 1
  },
  "radars": [
    {
      "update_id": "efbf7c77-c1ae-4a0d-85b6-116674e9e0b1",
      "update_type": "radar_created",
      "code": 201,
      "message": "Radar created successfully",
      "completed_at": "2026-05-25T11:17:20Z",
      "bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
      "item_index": 0,
      "custom_fields": { "crm_account_id": "acc_001" },
      "status": "success",
      "data": { "radar_id": "2ffb1a07-ac95-4a6d-bc34-39f479f1c856" }
    },
    {
      "update_id": "a1b2c3d4-0001-4000-b000-000000000001",
      "update_type": "radar_failure",
      "record_id": null,
      "completed_at": "2026-05-25T11:17:26Z",
      "discovered_at": "2026-05-25T11:17:26Z",
      "bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
      "item_index": 1,
      "custom_fields": {},
      "status": "error",
      "code": 409,
      "message": "Conflict: Radar already exists",
      "errors": [
        {
          "field": "domain",
          "reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0.",
          "conflicting_radar_id": "3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0"
        }
      ],
      "refund_amount_usd": 0,
      "error_id": "45ed35e5-986c-4496-afd8-9a84f6baa410",
      "timestamp": "2026-05-25T11:17:26Z"
    }
  ]
}
```

### Radar Types

| Radar Type                   | Description                                                              |
| ---------------------------- | ------------------------------------------------------------------------ |
| `company_mentions`           | Social media and article mentions of your company                        |
| `company_social_posts`       | Company's own social media activity                                      |
| `company_social_posts_cxo`   | Social media posts from company executives                               |
| `company_social_engagements` | Social media engagements (likes, comments) on company posts              |
| `company_job_openings`       | New job postings from tracked companies                                  |
| `company_new_hires`          | New employee detections                                                  |
| `company_promotions`         | Employee promotions and role changes                                     |
| `company_reviews`            | Customer reviews across platforms                                        |
| `contact_job_changes`        | Individual contact career changes (departures, promotions, role changes) |
| `contact_social_posts`       | Social media posts from tracked contacts                                 |
| `contact_social_engagements` | Social media engagements (likes, comments) by tracked contacts           |
| `industry_mentions`          | Industry-wide mentions and news articles                                 |
| `industry_funding_rounds`    | Funding rounds across companies in your industry                         |
| `industry_job_openings`      | Job openings across companies in your industry                           |
| `industry_new_companies`     | New companies emerging in your industry                                  |

## Radar Failure Notifications

When a radar fails setup or is deactivated due to billing, you'll receive a `radar_failure` notification:

```json theme={null}
{
  "update_id": "550e8400-e29b-41d4-a716-446655440000",
  "update_type": "radar_failure",
  "record_id": null,
  "discovered_at": "2024-03-20T12:30:00Z",
  "completed_at": "2024-03-20T12:30:00Z",
  "data": {
    "radar_id": "54e32291-25a4-459f-8bd6-4f79489ea931",
    "radar_type": "company_job_openings",
    "domain": "example.com",
    "custom_fields": {
      "priority": "high"
    }
  },
  "status": "error",
  "code": 402,
  "message": "Radar was deactivated due to insufficient credits.",
  "errors": [{ "field": "radar", "reason": "insufficient_funds" }],
  "refund_amount_usd": 0,
  "timestamp": "2024-03-20T12:30:00Z",
  "error_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

When setup fails, the same `radar_failure` shape is used with a different `code` and refund value:

```json theme={null}
{
  "update_id": "550e8400-e29b-41d4-a716-446655440001",
  "update_type": "radar_failure",
  "record_id": null,
  "discovered_at": "2024-03-20T12:30:00Z",
  "completed_at": "2024-03-20T12:30:00Z",
  "data": {
    "radar_id": "54e32291-25a4-459f-8bd6-4f79489ea931",
    "radar_type": "company_social_posts",
    "domain": "example.com",
    "custom_fields": {
      "priority": "high"
    }
  },
  "status": "error",
  "code": 400,
  "message": "Radar creation failed because the domain does not have required social media accounts.",
  "errors": [{ "field": "radar", "reason": "missing_prerequisites" }],
  "refund_amount_usd": 0.10,
  "timestamp": "2024-03-20T12:30:00Z",
  "error_id": "550e8400-e29b-41d4-a716-446655440001"
}
```

### Failure Reasons

Identify the failure type from `code` and `message`. The `errors[].reason` field contains the machine-readable reason key.

| `code` | `errors[].reason`       | Description                                                              |
| ------ | ----------------------- | ------------------------------------------------------------------------ |
| `400`  | `missing_prerequisites` | Required accounts or data sources not found                              |
| `400`  | `setup_failure`         | General setup failure during radar initialization                        |
| `400`  | `source_inaccessible`   | Target source (e.g. LinkedIn profile, domain) is inaccessible or private |
| `402`  | `insufficient_funds`    | Radar deactivated — balance too low to cover the charge                  |

## Best Practices for Webhook Handling

1. **Respond Quickly**: Return a 2xx status code immediately, then process asynchronously
2. **Implement Retry Logic**: Be prepared to handle the same event multiple times
3. **Verify Signatures**: Check webhook signatures to validate authenticity (documentation coming soon)
4. **Track Failure Events**: Monitor `radar_failure` to handle billing/setup problems quickly
5. **Handle Null Fields**: Some fields like `record_id` may be null for certain event types

## Testing Your Webhook

### Quick test

Trigger a sample payload to your endpoint:

```bash theme={null}
curl -X POST https://api.tamradar.com/v1/webhooks/test \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://your-domain.com/webhook",
    "radar_type": "company_job_openings"    // any supported radar_type
    // "update_type": "radar_failure"        // optional, defaults to radar_finding
  }'
```

The test event is fire-and-forget, with no retries or signatures, and uses the same payload format you'll receive in production.

Need help? [Contact our team](mailto:support@tamradar.com)
