---
title: SIEM Integration
description: Forward 1Security audit logs, monitoring alerts, and security alerts into Splunk, Microsoft Sentinel, QRadar, Elastic, or any SIEM that can poll a REST endpoint.
icon: Radar
---

This guide covers wiring 1Security into a SIEM end to end: what to forward, the
polling loop that keeps the feed complete, and how to connect it to the common
platforms. The API contract itself - every endpoint, parameter, and field - is
in the [API reference](/en/docs/reference/api).

The model is **pull-based**. Your SIEM polls on a schedule and stores a
watermark, so each poll fetches only what is new. No inbound connectivity to
your network is required.

## Before you start

You need an API key with the scopes for the feeds you plan to forward. Create
it in the dashboard under **Settings → API Keys** (admin only) and copy the
`1sec_live_…` secret - it is shown once.

Confirm it works before building anything around it:

```bash
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
  https://api.1security.ai/api/v1/ping
```

The response echoes the tenant and the scopes the key carries. If a scope you
expected is missing, fix it now rather than debugging a `403` from inside a
connector.

## 1. Decide what to forward

The three feeds answer different questions, and they have very different
volumes. Most teams start with the two alert feeds and add logs once the
mapping is settled.

| Feed                 | Volume                        | Forward it when                                                                                                 |
| -------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `/security-alerts`   | Low                           | Always. These are Defender / Sentinel detections, already enriched with the users, groups, and apps they touch. |
| `/monitoring-alerts` | Low                           | Always. This is what your own 1Security policies detected, and it exists nowhere else.                          |
| `/logs`              | High - the full activity feed | You want M365 activity in the SIEM with permission, device, and location context attached.                      |

<Callout type="info">
  On a busy tenant `/logs` is the feed that drives SIEM licence cost. Filter at
  the API rather than after ingest: `severity=high,critical` or a specific
  `action` list keeps the volume proportional to what your detections actually
  use. You can always widen it later.
</Callout>

## 2. The polling loop

Poll a **closed time window** and move the watermark only after the window has
drained completely.

Closing the window is what makes this reliable. An open-ended query keeps
growing while you page through it - new events land at the head of the ordering
and shift rows between requests. A window with both ends bound is a fixed set:
it cannot change while you read it.

<Steps>
  <Step>
    ### Window by ingestion time
    For `/logs`, use `discoveredFrom` and `discoveredTo`, not `from` and `to`.
    `discoveredAt` is when 1Security ingested the event; `occurredAt` is when it
    happened in M365. Microsoft can surface events hours after the fact, and
    only ingestion time is monotonic - so only ingestion time guarantees you
    never miss a late arrival.

    For the alert feeds, `from` and `to` window on when the alert was raised.

  </Step>
  <Step>
    ### Leave a short lag
    End the window a minute or two behind the current clock rather than at
    "now". Events are still committing at the boundary, and a small lag keeps
    one from landing just after you read past it.
  </Step>
  <Step>
    ### Sort ascending and drain
    Add `sort=discoveredAtAsc` on `/logs`. That ordering is tie-broken by event
    id, so every row has exactly one position in the page sequence. Follow
    `pagination.nextCursor` with `?cursor=` until `hasMore` is `false`.
  </Step>
  <Step>
    ### Advance only on success
    Move the watermark to the **end of the window you just drained**, and only
    after every page succeeded. If any page fails, keep the old watermark and
    retry the whole window - re-reading is cheap, and `id` dedupe absorbs the
    overlap.
  </Step>
</Steps>

A complete poller, small enough to read in one sitting:

```bash
#!/usr/bin/env bash
set -euo pipefail

STATE_FILE=/var/lib/1security/watermark
LAG_SECONDS=120   # stay slightly behind now, so nothing commits past the edge

SINCE=$(cat "$STATE_FILE" 2>/dev/null || date -u -d '-1 hour' +%FT%TZ)
UNTIL=$(date -u -d "-${LAG_SECONDS} seconds" +%FT%TZ)

BASE="https://api.1security.ai/api/v1/logs"
QUERY="discoveredFrom=$SINCE&discoveredTo=$UNTIL&sort=discoveredAtAsc&limit=1000"
CURSOR=""

while :; do
  URL="$BASE?$QUERY"
  [ -n "$CURSOR" ] && URL="$URL&cursor=$CURSOR"

  RESP=$(curl -sS --fail-with-body \
    -H "Authorization: Bearer $ONESEC_API_KEY" "$URL")

  echo "$RESP" | jq -c '.data[]' >> /var/log/1security-logs.ndjson

  CURSOR=$(echo "$RESP" | jq -r '.pagination.nextCursor // empty')
  [ -z "$CURSOR" ] && break
done

# Reached only if every page succeeded.
echo "$UNTIL" > "$STATE_FILE"
```

Run it every one to five minutes. Overlapping windows slightly and relying on
`id` dedupe in the SIEM is safer than trying to hit exact boundaries.

## 3. The first run

Backfilling history is the same loop with a fixed sequence of windows instead of
one rolling window. 1Security retains activity for up to three years, so decide
how far back your SIEM actually needs before you start.

- Walk the range in **fixed chunks** - one to six hours each, depending on
  tenant size. Each chunk is a closed window, so each is independently
  restartable.
- Keep `limit=1000` and stay inside the rate limit of 600 requests per minute.
- Log which chunk you are on. If the backfill dies, you resume at that chunk
  rather than from the beginning.
- Run the backfill and the live poll as **separate jobs** with separate
  watermarks. Let the live poll start from now, and let the backfill work
  backwards behind it, so live coverage is never waiting on history.

## 4. Alerts change after they are raised

Logs are immutable: once ingested, an event never changes. Alerts are not - they
get assigned, snoozed, and resolved after they first appear. A watermark-only
poll captures the moment an alert was raised and never sees what happened to it
afterwards.

If your SOC works alerts inside the SIEM, add a **reconciliation sweep** next to
the incremental poll: on a slower schedule, say hourly, re-fetch alerts raised
in the last N days regardless of the watermark and upsert them on `id`. State
changes then land within one sweep.

```bash
# Hourly reconciliation - re-read the last 7 days and upsert on id
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
  "https://api.1security.ai/api/v1/monitoring-alerts?from=$(date -u -d '-7 days' +%FT%TZ)&limit=1000"
```

Size the sweep window to how long an alert typically stays open in your process.
`isResolved` and `resolvedAt` tell you where each one ended up.

## 5. Wire it into your SIEM

<Tabs items={['Microsoft Sentinel', 'Splunk', 'QRadar / Elastic', 'Plain cron']}>
  <Tab value="Microsoft Sentinel">
    Use the **Codeless Connector Platform**, or a **Logic App** if you want
    explicit control over the loop.

    - **Auth**: `Authorization: Bearer <key>` as a connection secret.
    - **Paging**: follow `pagination.nextCursor` into the `cursor` query
      parameter; stop when `hasMore` is `false`.
    - **Destination**: post `data[]` to a custom table in Log Analytics through
      a Data Collection Endpoint and rule.
    - **TimeGenerated**: map from `occurredAt` (logs), `lastScan` (monitoring
      alerts), or `firstActivityDateTime` (security alerts).
    - **State**: store the window end in the connector's state, and advance it
      only after a complete drain.

    Because 1Security also ingests Defender and Sentinel alerts, forwarding
    `/security-alerts` back into Sentinel can duplicate what is already there.
    Most teams forward `/monitoring-alerts` and `/logs` to Sentinel and leave
    `/security-alerts` for SIEMs that do not already have that source.

  </Tab>

  <Tab value="Splunk">
    Use a **REST API modular input** (Splunk Add-on Builder, or an equivalent
    add-on).

    - **Endpoint**: one input per feed you forward.
    - **Auth header**: `Authorization: Bearer <key>`.
    - **Response handler**: index each element of `data[]` as its own event.
    - **Event time**: extract from `occurredAt` / `lastScan` /
      `firstActivityDateTime` rather than using index time, so late-arriving
      M365 events land on the right timeline.
    - **Dedupe**: set `id` as the event key so overlapping windows collapse.
    - **Checkpoint**: persist the window end between runs.
    - **Schedule**: every one to five minutes.

  </Tab>

  <Tab value="QRadar / Elastic">
    Both platforms poll JSON over REST natively.

    - **QRadar**: a log source using the **Universal REST API Protocol**, with
      the bearer token as a header, `data[]` as the record path, and
      `pagination.nextCursor` driving the paging parameter.
    - **Elastic**: the **httpjson** input in Elastic Agent or Filebeat. Split on
      `data[]`, use a cursor variable for `nextCursor`, and set `@timestamp`
      from the event time field.

    In both cases keep the closed-window pattern from section 2 - configure the
    request to send `discoveredFrom` and `discoveredTo` rather than an
    open-ended query.

  </Tab>

  <Tab value="Plain cron">
    The script in section 2 is the whole integration. It writes NDJSON, which
    every log shipper can tail - Fluent Bit, Vector, Filebeat, or a plain
    forwarder.

    Run it from cron or a systemd timer every one to five minutes. Keep the
    watermark file on persistent storage, and make sure only one instance runs
    at a time (`flock`) so two runs cannot advance the watermark past a window
    neither finished.

  </Tab>
</Tabs>

## 6. Field mapping

| Use for      | `/logs`                           | `/monitoring-alerts`        | `/security-alerts`         |
| ------------ | --------------------------------- | --------------------------- | -------------------------- |
| Event time   | `occurredAt`                      | `lastScan`                  | `firstActivityDateTime`    |
| Ingest time  | `discoveredAt`                    | -                           | -                          |
| Dedup key    | `id`                              | `id`                        | `id`                       |
| Severity     | `severity`                        | `severity`                  | `severity`                 |
| Actor        | `actorName`, `actorId`, `actorIp` | `assignedUser`              | `actorDisplayName`         |
| Resource     | `resourceName`, `resourceType`    | `resources`, `resourceType` | `users`, `groups`, `apps`  |
| Rule / title | `action`                          | `name`                      | `title`                    |
| Status       | -                                 | `status`, `isResolved`      | `status`, `classification` |

Two normalization details worth handling up front:

- **Severity vocabularies differ.** Logs and monitoring alerts use
  `info · low · medium · high · critical`. Security alerts follow Microsoft's
  scale: `informational · low · medium · high · unknown`. Map both into your
  SIEM's own scale rather than passing the strings through.
- **Timestamps.** Logs and security alerts return full ISO-8601 with a `Z`.
  Monitoring alerts currently return the same UTC instant without the `T` and
  `Z` (`2026-06-05 09:12:44`). Parse it as UTC. A future release unifies this on
  ISO-8601 everywhere.

## 7. Operating the integration

<Accordions>
  <Accordion title="Handling errors">
    `429` - wait for the seconds given in `Retry-After`, then retry the same
    request. `500` - retry with exponential backoff; do not advance the
    watermark. `401` - the key was revoked or expired; the integration is down
    until it is replaced, so alert on this rather than retrying. `403` - the key
    is missing a scope, which is a configuration problem, not a transient one.
  </Accordion>
  <Accordion title="Where the watermark lives">
    Persist it outside the process - a state file, the connector's own state
    store, or a KV entry. A watermark held only in memory silently restarts the
    feed from its default on every deploy, which shows up as a duplicate storm
    or a gap, depending on the default.
  </Accordion>
  <Accordion title="Monitor the integration itself">
    Add a dead-man check: alert if no 1Security events have been indexed for
    longer than a few polling intervals. A poller that dies quietly looks
    exactly like a quiet tenant, and that is the failure you find out about
    during an investigation.
  </Accordion>
  <Accordion title="Controlling volume">
    Filter at the API, not after ingest. Narrow `/logs` with `severity`,
    `action`, or `workload`, and widen it as your detections need more. For
    security alerts, forward the list endpoint and fetch `/security-alerts/{id}`
    on demand during triage instead of indexing the full `rawData` payload for
    every alert.
  </Accordion>
  <Accordion title="Rate limits">
    600 requests per minute per key. One poll per feed per minute at
    `limit=1000` uses a small fraction of that. Backfills are the only realistic
    way to approach it - throttle those rather than the live poll.
  </Accordion>
</Accordions>

## What is coming

Planned improvements to this integration path, so you can design around them:

- **Webhook push delivery**, removing the polling loop for alert feeds.
- **An OpenAPI specification**, plus reference connectors for Sentinel and
  Splunk, so the wiring in section 5 becomes an import rather than a build.
- **Organization-level keys** for MSSPs, so one connector covers a whole
  portfolio of tenants instead of one key per tenant.
- **Bidirectional sync**, so resolving an alert in the SIEM resolves it in
  1Security.

## Next

<Cards>
  <Card
    title="API reference"
    href="/en/docs/reference/api"
    description="Every endpoint, parameter, response field, and error code."
  />
  <Card
    title="Activity logs"
    href="/en/docs/screens/activity-logs"
    description="What the log feed contains and how 1Security enriches each event."
  />
</Cards>
