SysEngineering

Okta API Ping

Keeps the per-entity TriNet → Okta API tokens alive.

Okta deactivates an API token after 30 days of inactivity. Each TriNet entity’s HR-to-Okta integration uses its own SSWS token; entities that go a while without hiring never exercise their token, so it silently dies and the next new hire fails to provision. This script pings every token weekly with a cheap read-only call, which resets the 30-day idle clock.

It does double duty:

⚠️ Pinging cannot revive a dead token, and Okta only shows a token’s value once at creation. A token that’s already expired (or whose value you never saved) has to be regenerated in Okta and re-entered into TriNet by whoever owns the integration config. This tool tells you which ones; it can’t recover them.

How it works

GitHub Actions cron (~every 10 days: 1st/11th/21st, 08:00 UTC)
        │
        ▼
   ping.py
        │  for each label -> token in OKTA_PING_TOKENS:
        ├──► GET https://<OKTA_DOMAIN>/api/v1/users?limit=1   (Authorization: SSWS <token>)
        │       200 → healthy (idle timer reset)
        │       401 → dead    (regenerate + re-enter in TriNet)
        │       else → errored (403 scope / 429 / 5xx / network)
        │
        ├──► Slack incoming webhook  (only when something needs attention,
        │                             or always if ALWAYS_NOTIFY=true)
        │
        └──► Confluence "API Key Status" table  (optional — see below)

The run exits non-zero if any token is dead or errored, so the failed GitHub Actions run is itself a backstop signal even if Slack delivery is misconfigured.

Config

All config is via environment variables (or a local .env — see .env.example):

Var Required Example
OKTA_DOMAIN yes schmidtentities.okta.com
OKTA_PING_TOKENS yes {"TriNet - Hillspire":"00aBc...","TriNet - Schmidt Ocean":"00dEf..."}
SLACK_WEBHOOK_URL no https://hooks.slack.com/services/T.../B.../...
ALWAYS_NOTIFY no true to post a heartbeat even when all healthy
CONFLUENCE_BASE_URL no¹ https://hillspire.atlassian.net/wiki
CONFLUENCE_EMAIL no¹ Audit Bot account email
CONFLUENCE_API_TOKEN no¹ Audit Bot Atlassian API token
CONFLUENCE_SPACE_KEY no¹ IPO
CONFLUENCE_KEYLOG_PAGE_ID no¹ page ID of the “Okta API Key Log” index page
OKTA_API_TOKEN no² service-account SSWS token with System Log read

¹ Required only to enable the Confluence API Key Log (below) — all five together, or none. ² Required only by keylog.py.

OKTA_PING_TOKENS is a single JSON object mapping a human label to each entity’s SSWS token, kept on one line. Label each entry clearly (e.g. "TriNet - <Entity>") so a dead-token alert points you straight at the entity whose integration needs fixing.

Confluence API Key Log (3.C.2) — optional

If the CONFLUENCE_* settings are present, the integration maintains a Confluence log that mirrors the Okta Audit Bot’s 3.C.1 structure — a folder, an index page, and a page per month:

3.C.2 Okta API Key Log              (folder — create manually in the UI)
└─ Okta API Key Log                 (index page = CONFLUENCE_KEYLOG_PAGE_ID)
      • API Key Status   table  ← ping.py rewrites every keepalive run
      • Monthly Log      table  ← Month | Page | Created | Revoked
   └─ 2026-06 Okta API Key Log      (monthly pages, auto-created)
         • lifecycle events  ← keylog.py appends create/revoke
ping.py    (~every 10 days)  ─► rewrites the index "API Key Status" table
keylog.py  (every 15 min)    ─► appends create/revoke to the month page (near real-time)

The two share a GitHub Actions concurrency group so they never write at once; update_page also retries once on a version conflict. State (last System Log timestamp) lives in a content property on the index; events are deduped by timestamp — same approach as okta_audit_log.

Reuse the Okta Audit Bot service account for CONFLUENCE_* and OKTA_API_TOKEN (it already has System Log read). OKTA_API_TOKEN is the read-only service token — not one of the TriNet tokens in OKTA_PING_TOKENS.

Setup

  1. In Confluence under 3.C - Okta, create a folder named 3.C.2 Okta API Key Log (same as the 3.C.1 folder). Inside it, create an empty page titled Okta API Key Log — that’s the index. Grab the index page’s ID from its URL (.../pages/<id>/); the script installs the template and creates monthly pages under it.
  2. Add the secrets: CONFLUENCE_BASE_URL, CONFLUENCE_EMAIL, CONFLUENCE_API_TOKEN, CONFLUENCE_SPACE_KEY, CONFLUENCE_KEYLOG_PAGE_ID, OKTA_API_TOKEN (all but the page ID reuse the Audit Bot’s values).
  3. Trigger Actions → Okta API Key Log → Run workflow once to bootstrap.

One-time setup

1. Consolidate the token values

For any token you don’t have, generate a fresh one (Okta Admin Console → Security → API → Tokens → Create Token), record its value, and have your HRIS contact enter it into that entity’s TriNet integration. The creating account needs the same privileges TriNet uses to provision users.

Rather than hand-writing the OKTA_PING_TOKENS JSON, keep a friendly tokens.txt (gitignored — see tokens.example.txt) with one Label = token per line, then let the helper assemble the secret value:

python integrations/okta-api-ping/build-tokens.py            # prints the one-line JSON
python integrations/okta-api-ping/build-tokens.py --env      # prints OKTA_PING_TOKENS=...

Start with the few tokens you have and add lines as you mint the rest — the keepalive script pings whatever ends up in the secret.

api.slack.com/apps → your app → Incoming Webhooks → add one for the channel you want alerts in. Copy the https://hooks.slack.com/... URL.

3. Add GitHub secrets

Repo settings → Secrets and variablesActions:

Secret Value
OKTA_DOMAIN schmidtentities.okta.com
OKTA_PING_TOKENS the one-line JSON object of label → token
SLACK_WEBHOOK_URL webhook for ping.py dead-token alerts (omit to disable)
KEYLOG_SLACK_WEBHOOK_URL webhook for keylog.py key create/revoke alerts (omit to disable)

Both scripts read the same SLACK_WEBHOOK_URL environment variable, but each workflow maps a different secret into it — okta_api_keylog.yml maps KEYLOG_SLACK_WEBHOOK_URL. That keeps the two alert paths independent: turning on key-log alerts can’t accidentally switch on dead-token alerts, and they can point at different channels.

OKTA_DOMAIN is already a secret if you’ve set up the okta_audit_log integration — the same value is reused here.

4. Run it once

ActionsOkta API PingRun workflow. The first run is your audit: check the logs (and Slack) for any dead/errored tokens, fix those, then let the weekly schedule take over.

Local run (audit without alerting)

cp integrations/okta-api-ping/.env.example integrations/okta-api-ping/.env
# fill in OKTA_DOMAIN and OKTA_PING_TOKENS, then:
python -m pip install -r integrations/okta-api-ping/requirements.txt
python integrations/okta-api-ping/ping.py --no-slack

--no-slack logs the summary instead of posting it — useful when you just want to see token health locally.

Notes