Collecting Okta System Log events
There is no Okta poller — you forward. How to do it, what Okta's own fields become, why replaying a page is safe, and which events land in the catch-all category.
Okta's System Log is the record of every authentication, every administrative change and every application assignment in your identity provider. It is the single most useful non-Microsoft log source a small organisation can connect, because almost every attack that matters passes through it.
Setting it up
Issue an ingest token with the Okta parser
POST /api/v1/organizations/{orgID}/siem/ingest-tokenswith{"name": "okta", "parser_kind": "okta"}. Requiressiem:sources.write. Capture the plaintext from the response; it is shown once. See Ingest tokens.Point a forwarder at the collector
POST /api/v1/siem/hecwithAuthorization: Bearer <token>andContent-Type: application/json. Okta's own log-streaming feature, an AWS EventBridge relay, Vector, Fluent Bit or a cron job runningcurlagainst the System Log API all work — nothing about the collector is Okta-specific.Post the API's array response unchanged
The System Log API returns a JSON array of
LogEventobjects. You can post that array verbatim: the collector splits a top-level array into one record per element, and the Okta mapper additionally accepts an array in a single record. You do not need to reshape anything.Confirm events arrived
Search
source_module = "siem-ingest" AND category = authenticationand look for your Okta usernames inuser_id. The token'sevents_ingestedcounter also climbs on every accepted request.
What Okta's fields become
This is a full mapper written against Okta's record shape rather than a generic JSON reader, so a great deal survives. The table covers the fields that reach the searchable columns; a good deal more is preserved in the stored document for detection rules.
| Okta field | Becomes | Note |
|---|---|---|
published | The event timestamp | Okta's own clock, parsed with sub-second precision. |
uuid | The event identifier and dedupe key | See above. |
eventType | The action, on the catch-all path | Verbatim, dots and all: application.user_membership.add. |
actor.alternateId | user_id — sometimes | Only when the event names no user target. See the warning below. |
client.ipAddress | source_ip | Searchable. |
client.geographicalContext | City, region and country on the stored document | Not searchable. Available to a detection rule, not to the query language. |
outcome.result | outcome | SUCCESS becomes success, FAILURE and DENY become failure. |
outcome.reason | The status detail on the stored document | Not searchable directly. |
severity | severity | Honoured rather than flattened — see below. |
target[] | asset_id, and the user or group the event is about | The first target's display name reaches asset_id on the catch-all path. |
debugContext.debugData | Preserved under unmapped | Every key, prefixed debugData.. Reachable by a rule at metadata.ocsf.unmapped.debugData.<key>. |
securityContext.isProxy, asOrg | Preserved under unmapped | Useful signals; not searchable. |
Which events land where
Okta has hundreds of event types. They sort into three buckets, and only two of them get a specific vocabulary.
| Okta event types | `category` | `action` |
|---|---|---|
user.session.*, user.authentication.*, user.mfa.factor.verify, Okta Verify MFA events | authentication | login_success, login_failed, logout, mfa_success or mfa_failed, from the outcome and the event type. |
user.lifecycle.*, user.account.*, group.user_membership.*, user.mfa.factor.activate and .deactivate | iam | user_created, user_deleted, account_enabled, account_disabled, password_changed, password_reset, account_locked, account_unlocked, role_assigned, permission_revoked, mfa_enabled, mfa_disabled. |
| Everything else — app assignment, policy changes, API token use, system events | api | The Okta eventType, verbatim. |
The first two buckets are the valuable ones, because they share a vocabulary with your Windows, Entra and Google Workspace events. A brute-force rule written against action = login_failed matches an Okta failure and a Windows 4625 without knowing the difference, which is the entire point of normalising in the first place.
Two consequences of the same design worth planning around. Application assignment and unassignment — a genuinely security-relevant pair — land in api rather than iam, so a rule scoped to identity changes will not see them. And Okta's policy and sign-on rule changes also land in api, so somebody weakened the MFA policy is a search you have to write against the event type rather than a category.
Okta's severity is honoured
Unlike the appliance parsers, this mapper reads the source's own severity and translates it, then applies one refinement: an informational event that failed is raised a step, because a failure is more interesting than a success at the same level.
| Okta `severity` | Outcome | Searchable `severity` |
|---|---|---|
DEBUG or INFO | Success | 2 |
DEBUG or INFO | Failure | 4 |
WARN | Any | 6 |
ERROR | Any | 8 |
| Anything unrecognised | Failure | 6 |
| Anything unrecognised | Success | 2 |
So severity >= 8 on Okta data means Okta itself called this an error, and severity >= 4 is the useful floor for something failed. Authentication and account-change events are additionally classified as privileged audit data, which is the classification that governs how they are retained — see Log retention.
Queries worth saving
# Failed sign-ins for one account
category = authentication AND outcome = failure AND user_id = "[email protected]"
# Every identity change, newest first
category = iam
# Accounts created or deleted
action = user_created OR action = user_deleted
# MFA factors being removed — a classic account-takeover step
action = mfa_disabled
# Application assignments, which land in the catch-all
action contains user_membership
# Anything Okta itself flagged as an error
severity >= 8 AND raw_log contains oktaThat last one needs the raw_log clause because severity >= 8 on its own will also return high-severity events from every other source you have connected. There is no source field in the query language that distinguishes Okta from Windows — source_module reads siem-ingest for everything that arrives through log collection.
The limits, stated together
- No poller. Forwarding is yours to build and yours to monitor. If your job stops, nothing here notices — there is no this source has gone quiet alert for a source the platform does not own. Check the token's
events_ingestedcounter periodically, or build the check into your job. - No geo search. Country and city are mapped and stored and are not in the fifteen searchable fields. Sign-ins from an unexpected country is a detection rule, not a search.
- Application assignment is not an identity event in the category vocabulary, as above.
- Rate limits apply per token: 1,000 events per second steady, 2,000 burst. An initial backfill of a busy tenant can exceed that; page it rather than firing the whole history at once.
- Okta's own API rate limits apply at your end, and are usually the tighter constraint on a backfill.
What to read next
- Sending events with the HTTP Event Collector — the endpoint, the headers and the exact success response.
- How your logs are normalised — the shared vocabulary that makes an Okta failure and a Windows failure the same event.
- Connecting your first log source — the per-source status table for everything else.