Skip to main content

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.

Written for whoever runs IT10 min readUpdated

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

  1. Issue an ingest token with the Okta parser

    POST /api/v1/organizations/{orgID}/siem/ingest-tokens with {"name": "okta", "parser_kind": "okta"}. Requires siem:sources.write. Capture the plaintext from the response; it is shown once. See Ingest tokens.

  2. Point a forwarder at the collector

    POST /api/v1/siem/hec with Authorization: Bearer <token> and Content-Type: application/json. Okta's own log-streaming feature, an AWS EventBridge relay, Vector, Fluent Bit or a cron job running curl against the System Log API all work — nothing about the collector is Okta-specific.

  3. Post the API's array response unchanged

    The System Log API returns a JSON array of LogEvent objects. 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.

  4. Confirm events arrived

    Search source_module = "siem-ingest" AND category = authentication and look for your Okta usernames in user_id. The token's events_ingested counter 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 System Log fields and where they end up.
Okta fieldBecomesNote
publishedThe event timestampOkta's own clock, parsed with sub-second precision.
uuidThe event identifier and dedupe keySee above.
eventTypeThe action, on the catch-all pathVerbatim, dots and all: application.user_membership.add.
actor.alternateIduser_id — sometimesOnly when the event names no user target. See the warning below.
client.ipAddresssource_ipSearchable.
client.geographicalContextCity, region and country on the stored documentNot searchable. Available to a detection rule, not to the query language.
outcome.resultoutcomeSUCCESS becomes success, FAILURE and DENY become failure.
outcome.reasonThe status detail on the stored documentNot searchable directly.
severityseverityHonoured rather than flattened — see below.
target[]asset_id, and the user or group the event is aboutThe first target's display name reaches asset_id on the catch-all path.
debugContext.debugDataPreserved under unmappedEvery key, prefixed debugData.. Reachable by a rule at metadata.ocsf.unmapped.debugData.<key>.
securityContext.isProxy, asOrgPreserved under unmappedUseful 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.

The three routes an Okta event takes, with the searchable category and action each produces.
Okta event types`category``action`
user.session.*, user.authentication.*, user.mfa.factor.verify, Okta Verify MFA eventsauthenticationlogin_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 .deactivateiamuser_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 eventsapiThe 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 to the platform's 0-10 scale, as it appears in search.
Okta `severity`OutcomeSearchable `severity`
DEBUG or INFOSuccess2
DEBUG or INFOFailure4
WARNAny6
ERRORAny8
Anything unrecognisedFailure6
Anything unrecognisedSuccess2

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

Okta questions you will be asked
# 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 okta

That 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_ingested counter 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

  1. Sending events with the HTTP Event Collector — the endpoint, the headers and the exact success response.
  2. How your logs are normalised — the shared vocabulary that makes an Okta failure and a Windows failure the same event.
  3. Connecting your first log source — the per-source status table for everything else.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in logs and detection