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

# Generic Detection

> Send detections from any system to Wirespeed with a webhook

The Generic Detection integration turns any system that can send an HTTPS POST — a SIEM, an internal tool, a script, or another security product — into a detection source for Wirespeed. You send one detection per request in the documented JSON format, and Wirespeed triages it like a detection from any other integration: assets are extracted, the detection is categorized with the category you supply, and it appears in **Detections** and in the SIEM.

This is the integration to use when your system already produces finished detections. If you want Wirespeed to find detections in raw log data instead, use [Generic JSON](https://docs.wirespeed.co/integrations/generic-json.md) or a dedicated integration for your product.

## Setup in Wirespeed

1. Login to Wirespeed and navigate to Integrations > [**Add Integration**](https://app.wirespeed.co/settings/integrations?tab=browse) > **Generic Detection**
2. Enter a **Source Name** to identify this sending system (for example, `Internal SIEM`)
3. Optionally turn on **Authentication** (see [Authentication](#authentication))
4. Click **Integrate**
5. Select **Webhook Details**
6. Copy the **Webhook URL** and, if you enabled authentication, the **Webhook Secret**

The Webhook URL has the form:

```text theme={null}
https://<API_HOST>/integration/webhook/generic-detection/receive-detection?teamId=<TEAM_ID>&integrationId=<INTEGRATION_ID>
```

Create one Generic Detection integration per sending system. Each integration has its own URL, its own optional secret, and its own Source Name, so deliveries stay attributable and one system's secret can be rotated without affecting another.

## Authentication

Authentication is optional and off by default. When **Authentication** is on, every request must include the integration's webhook secret in the `X-Webhook-Key` header:

```text theme={null}
X-Webhook-Key: <WEBHOOK_SECRET>
```

Requests with a missing or incorrect key are rejected with `401` and the rejection is written to the integration's log (at most once every five minutes, so a misconfigured sender cannot flood the log). The secret is never accepted in the URL or the request body.

### Rotating the secret

1. Open the integration's **Webhook Details**
2. Click **Rotate secret**
3. Update the sender with the new secret

The old secret stops working as soon as the rotation completes. There is no grace period, so update the sender promptly; deliveries sent with the old secret in the meantime are rejected with `401` and are not queued.

## Sending a detection

Send one detection per `POST` request with a JSON body:

```bash theme={null}
curl -X POST '<WEBHOOK_URL>' \
  -H 'Content-Type: application/json' \
  -H 'X-Webhook-Key: <WEBHOOK_SECRET>' \
  -d '{
    "id": "alert-9042",
    "source": "internal-siem",
    "title": "Impossible travel sign-in",
    "description": "User signed in from two countries within one hour without a VPN session.",
    "severity": "high",
    "category": "IDENTITY__LOGIN",
    "occurredAt": "2026-11-13T15:00:00Z",
    "assets": {
      "users": [{ "email": "jane.doe@example.com", "username": "jane.doe" }]
    }
  }'
```

Omit the `X-Webhook-Key` header if you did not enable authentication. A `200` response means the detection was accepted and queued; it does not wait for triage to finish.

## Payload reference

Unknown fields are rejected, including inside `assets`, so a typo fails the request instead of being silently dropped.

| Field | Required | Description |
| - | - | - |
| `id` | No | Stable unique ID from your system (max 256 characters). Retries that reuse the same `id` deduplicate, so always send one if your system has it. |
| `source` | No | Sending system name (max 128 characters). Stored on the SIEM row; use it to filter one source when one integration fronts several senders. |
| `title` | Yes | Short summary of the detection (max 512 characters). |
| `description` | Yes | What happened and why it matters (max 10,000 characters). This is what analysts read during triage. |
| `severity` | Yes | One of `informational`, `low`, `medium`, `high`, `critical`. |
| `category` | Yes | One of the [categories](#categories) below, in the standard Wirespeed format with double underscores. |
| `occurredAt` | Yes | ISO 8601 time the detected activity occurred, for example `2026-11-13T15:00:00Z`. |
| `firstSeenAt` | No | ISO 8601 time the activity was first seen. Must not be after `lastSeenAt`. |
| `lastSeenAt` | No | ISO 8601 time the activity was last seen. Must not be before `occurredAt` or `firstSeenAt`. |
| `referenceUrl` | No | `http` or `https` link back to the detection in your system. |
| `blocked` | No | Set `true` when your system blocked the activity. |
| `mitigated` | No | Set `true` when your system already mitigated or resolved the activity. |
| `assets` | Yes | At least one of `users`, `endpoints`, `files`, or `ips`. Each is an array; every asset in an array needs at least one identifying field (see below). |

### Assets

Assets tell Wirespeed which users, endpoints, files, or IP addresses the detection is about, so it can correlate the detection with your inventory. Users, endpoints, and files can also be contained if needed; IP addresses are used for correlation only. Send as many as the detection involves — up to 50 of each type in one request. Send only the fields you know: each user, endpoint, or file asset needs at least one of its fields, and each IP asset needs `address`.

| Asset | Fields |
| - | - |
| `assets.users[]` | `id` (your unique user ID), `email`, `username` |
| `assets.endpoints[]` | `id` (your unique endpoint ID), `hostname` |
| `assets.files[]` | `name`, `path`, `sha256` (64 hex characters), `sha1` (40 hex characters) |
| `assets.ips[]` | `address` (required): one IPv4 or IPv6 address, for example `203.0.113.7` or `2001:db8::1` |

Hashes are case-insensitive and are stored lowercase. A hash with the wrong length or non-hex characters fails the request.

Send each IP address as a bare address. A port (`203.0.113.7:443`), a CIDR range (`10.0.0.0/8`), or a hostname fails the request; send hostnames as endpoints instead. IPv6 addresses are stored lowercase.

### Categories

Pick the one category that best describes the detection. The list is deliberately short; for a suspicious detection, send the closest category. Use `OTHER__INFORMATIONAL_EVENT` only for activity that needs no response, because Wirespeed closes those detections as benign automatically.

Category values use the standard Wirespeed category format: uppercase segments joined by **double underscores**. For example, `IDENTITY__LOGIN` has two underscores between `IDENTITY` and `LOGIN` — `IDENTITY_LOGIN` and `identity-login` are not accepted.

| Category | Use for |
| - | - |
| `IDENTITY__LOGIN` | Suspicious or anomalous sign-ins |
| `ENDPOINT__EXECUTION` | Suspicious programs, commands, or malware running on an endpoint |
| `CLOUD__INVOCATION` | Suspicious cloud API calls |
| `NETWORK__INBOUND_CONNECTION` | Suspicious inbound network connections |
| `NETWORK__OUTBOUND_CONNECTION` | Suspicious outbound network connections |
| `DATA__DATA_TRANSFER` | Unusual data movement or exfiltration |
| `OTHER__INFORMATIONAL_EVENT` | Informational activity that needs no response; closed as benign automatically |

## Sample payloads

### Endpoint malware with a file asset

```json theme={null}
{
  "id": "edr-77120",
  "source": "custom-edr",
  "title": "Malware executed on workstation",
  "description": "The agent quarantined evil.exe after it injected into explorer.exe.",
  "severity": "critical",
  "category": "ENDPOINT__EXECUTION",
  "occurredAt": "2026-11-13T14:58:11Z",
  "referenceUrl": "https://edr.example.com/alerts/edr-77120",
  "blocked": true,
  "mitigated": true,
  "assets": {
    "endpoints": [{ "id": "endpoint-7", "hostname": "WS-001" }],
    "files": [
      {
        "name": "evil.exe",
        "path": "C:\\Users\\jane\\Downloads\\evil.exe",
        "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
      }
    ]
  }
}
```

### Network connection with IP address assets

```json theme={null}
{
  "id": "fw-30418",
  "source": "perimeter-firewall",
  "title": "Outbound connection to known command-and-control server",
  "description": "WS-014 opened a TLS session to an IP address on the command-and-control blocklist.",
  "severity": "high",
  "category": "NETWORK__OUTBOUND_CONNECTION",
  "occurredAt": "2026-11-13T09:41:27Z",
  "blocked": true,
  "assets": {
    "endpoints": [{ "hostname": "WS-014" }],
    "ips": [{ "address": "198.51.100.23" }, { "address": "2001:db8::1" }]
  }
}
```

### Minimal payload

Only the required fields. Wirespeed can triage this, but adding an `id` and asset identifiers improves deduplication and correlation.

```json theme={null}
{
  "title": "Unusual outbound transfer",
  "description": "Workstation uploaded 2 GB to an unknown host overnight.",
  "severity": "medium",
  "category": "DATA__DATA_TRANSFER",
  "occurredAt": "2026-11-13T07:12:00Z",
  "assets": {
    "endpoints": [{ "hostname": "WS-014" }]
  }
}
```

## Errors and edge cases

Rejected deliveries are never queued. When a delivery fails validation, the reason is written to the integration's log in Wirespeed so you can fix the sender; check there first when detections do not appear.

| Status | Meaning | What to do |
| - | - | - |
| `400` | Malformed or invalid payload: missing required field, unknown field, invalid `category` or `severity`, unparseable or out-of-order timestamps, an asset without identifiers, or a malformed email, URL, hash, or IP address. | Fix the field named in the integration log entry and resend. |
| `401` | Authentication is enabled and the `X-Webhook-Key` header is missing or does not match. | Send the current webhook secret. If you rotated it, update the sender. |
| `404` | The `teamId` or `integrationId` in the URL does not identify an integration. | Copy the Webhook URL from **Webhook Details** again. |
| `422` | Webhook processing is temporarily paused. | Retry with backoff. |
| `429` | Your team exceeded the webhook rate limit (3,000 requests/minute). | Retry with backoff; batch senders should spread deliveries out. |

Edge cases to know:

* **One detection per request.** Send an array and the request fails validation; loop over detections instead.
* **Retries are safe with an `id`.** The same `id` delivered twice becomes one detection. Without an `id`, a retry can create a duplicate.
* **Future timestamps** are clamped to the ingestion time for the detection's event time; the timestamps you sent are kept on the SIEM row.
* **Disabled integrations** reject deliveries until they are re-enabled.
* **Secrets in URLs are ignored.** A `webhookSecret` query parameter does not authenticate a request; use the `X-Webhook-Key` header.

## What Wirespeed does with it

Each accepted delivery becomes one first-class Wirespeed detection, the same as a detection from any built-in integration, and is triaged automatically: Wirespeed investigates it, reaches a verdict, and escalates or responds according to your team's automation settings, with no manual step needed. The users, endpoints, and files you sent are extracted for correlation and containment, the IP addresses for correlation, the detection is categorized with the `category` you sent, and the normalized payload is written to the `generic_detection_logs` SIEM table. In the SIEM you can filter that table by `severity`, `category`, and `source`; rows are stored ordered by team, then `source`, then time, so source-filtered queries stay fast. Each Generic Detection integration also appears as its own SIEM source under its **Source Name**, so deliveries from different sending systems stay separate even though they share one table.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.