Contents
Contents

Case Study - Building a PeopleSoft Okta Provisioning Engine

Building integrations between a classic relational database ERP like PeopleSoft and a modern cloud-based Identity and Access Management (IAM) system like Okta always reveals a clash of architectural eras. PeopleSoft was designed in the early 90s around transactional database commits, sequential batch schedules, and long-lived system sessions. Okta is built around REST APIs, JSON payloads, and dynamic user contexts.

In this case study, I’ll walk through the architectural design of a custom outbound provisioning engine we built to sync user lifecycle events from PeopleSoft Campus Solutions and HCM into Okta.

Instead of writing a simple sequential script, we designed an asynchronous, queue-based architecture using Integration Broker that handles provisioning at scale without blocking batch schedules or falling victim to external API timeouts.

The Core Problem

We were working with a large higher education institution where PeopleSoft serves as the system of record for students, faculty, staff, and contractors. When a student is admitted, a staff member is hired, or an employee updates their preferred name, those changes need to reflect in Okta in real time (or very close to it) so that downstream applications (like Office 365, Canvas, and Zoom) stay in sync.

The challenge: How do you make a single-threaded PeopleSoft environment orchestrate outbound REST API calls reliably, securely, and at high volume?

Two Processing Architectures Evaluated

We considered two primary ways to drive the outbound API calls.

Option 1: Sequential App Engine Loop

The simplest path would be an Application Engine (AE) batch process that queries a view of “changed users,” loops through each record, constructs a REST message, sends it, and waits for the HTTP response.

  • Why it looked good: Easy to write, single thread of execution, simple SQL select statement.
  • Why we rejected it: It is incredibly slow and operationally risky. Application Engines run sequentially. If a single Okta API call takes 500 milliseconds and you have a daily batch of 2,000 new students, the process will run for over 16 minutes just waiting on network I/O. In PeopleSoft, Process Scheduler slots are scarce. Holding a slot open while the system sits idle waiting for external HTTP responses blocks critical batch runs like payroll or financial posting. Furthermore, if the Okta API rate limits us or goes offline briefly, the entire batch halts.

Option 2: Asynchronous Integration Broker Queue

Instead of making the HTTP calls inside the App Engine loop, we split the work. The App Engine process acts solely as a “publisher.” It queries the changed records and quickly writes “provisioning requests” to custom queue tables.

An Integration Broker (IB) handler subscribes to these requests, picks up the messages asynchronously, and dispatches them.

  flowchart TD
    AppEngine["App Engine / Component\n(Event Publisher)"] -->|Writes Requests| Queue["Queue Tables\n(PS_CHG_OK_REQ_HDR/DTL)"]
    Queue -->|Local-to-Local Routing| IB["Integration Broker\nQueue"]
    IB -->|Concurrently Dispatched| Sub1["OnNotify Handler\nUser 1"]
    IB -->|Concurrently Dispatched| Sub2["OnNotify Handler\nUser 2"]
    IB -->|Concurrently Dispatched| SubN["OnNotify Handler\nUser N"]
    Sub1 -->|Apache HttpClient| Okta["Okta API"]
    Sub2 -->|Apache HttpClient| Okta
    SubN -->|Apache HttpClient| Okta
  • Why we chose it: It isolates the network I/O from the Process Scheduler. The App Engine publisher can queue 10,000 requests in a few seconds and exit. Integration Broker’s multi-threaded handler processes the queue in parallel, scaling throughput according to the application server’s thread configuration. If a single request fails due to an API timeout, it can be retried independently without affecting the rest of the batch.

The Queue Database Design

To implement the async queue, we built two custom tables.

Header Table: PS_CHG_OK_REQ_HDR

  • CHG_BATCH_ID — Unique key for the batch run
  • CHG_BATCH_STATUS — Batch status enum (NEW, INPR (In Progress), COMP (Complete), ERR (Error))
  • CREATED_DTTM — Timestamp when the batch was queued
  • LASTUPDDTTM — Last updated timestamp

Detail Table: PS_CHG_OK_REQ_DTL

  • CHG_BATCH_ID, LINE_NBR — Compound key linking to the header
  • OPRID — The PeopleSoft user ID being provisioned
  • CHG_LINE_ACTION — The API action: CR8I (Create Inactive), CR8A (Create Active), GRPA (Group Add), GRPR (Group Remove), USRA (Activate), USRD (Deactivate), USAU (Update Attributes)
  • CHG_LINE_STATUS — Status enum (NEW, INPR, COMP, ERR, WARN)
  • ERROR_MSG — Stack trace or error response body from Okta
  • CHG_OKTA_ID — The cached Okta GUID (populated after successful creation)

The Critical Design Pattern: Okta ID Caching

Every user in Okta is identified by a unique, random 20-character GUID (e.g., 00u2lvzhsoCLMZSGDJBY). Okta’s API endpoints require this GUID in the URL path for all update, activation, or group operations:

POST /api/v1/users/{okta_id}/lifecycle/activate
PUT  /api/v1/users/{okta_id}

If PeopleSoft only knows a user’s local identifier (like EMPLID or email) and needs to update their record, it must first execute a search query to Okta (GET /api/v1/users?filter=profile.email eq "user@univ.edu") to retrieve the GUID.

Doing a search before every write doubles your API call volume, increases latency, and burns through your Okta rate limits.

Our Solution: Cache the Okta GUID locally in a custom cross-reference table the first time the account is created or matched.

Table: PS_CHG_OPR_OKTA_ID

  • OPRID — PeopleSoft Operator ID
  • CHG_IDP_UTYPE — Identity Type (Student, Faculty, Staff, Adjunct)
  • CHG_OKTA_ID — The cached Okta GUID
  • CHG_OKTA_LOGIN_ID — The primary login email
  • LASTUPDDTTM — Timestamp of last sync

Subsequent runs join directly against this cross-reference table, retrieving the cached GUID so we can call the update endpoints directly.

Identity Profiles by Population

A one-size-fits-all provisioning strategy does not work in an enterprise. Different populations require different login formats, email sync policies, and group memberships. We defined these rules in a custom setup table PS_CHG_IDP_UTYPE:

Affiliation Login Format Primary Email Type Sync Phone Init State Okta Group Rules
Student [EMPLID]@student.univ.edu Home / Preferred No STAGED Add to Student Cohort
Staff [First].[Last]@univ.edu Business Yes ACTIVE Add to Staff General
Faculty [First].[Last]@univ.edu Business Yes ACTIVE Add to Faculty General
Adjunct [First].[Last]-ADJ@univ.edu Personal No STAGED Add to Adjunct Pool

When the sync engine builds details for a user, it queries the setup table to dynamically determine the payload structure and target state.

Five PeopleSoft Limitations and Workarounds

During the implementation, we hit several significant limitations in the PeopleTools platform.

1. PeopleTools JSON Parser Null Field Loss

The Problem: The native Integration Broker Document Technology JSON parser has a critical bug: it silently discards null values. If you attempt to parse {"email": null, "phone": "555-1234"}, the parser strips the email property from the structure entirely. In provisioning, setting a field to null is a deliberate command to clear it; stripping the field makes it look like the attribute was omitted, preventing updates. The Workaround: We bypassed the Document Technology parser. Instead, we used the native PeopleTools JsonParser and JsonObject classes introduced in PeopleTools 8.56, which preserve null values correctly. We also noted that the delivered Campus Community JSON wrapper (SCC_COMMON:JSON:JSONObject) in Campus Solutions is useful but has its own bugs, so we stuck to standard, native JsonParser methods.

2. Integration Broker Swallows Error Response Bodies

The Problem: When you make an outbound REST call using the standard %IntBroker.ConnectorRequest wrapper, if the server returns a non-2xx error code (like 400 Bad Request or 429 Too Many Requests), the wrapper throws a generic exception. It does not return the response body containing Okta’s detailed error payload (e.g., "login: login must be in the form of an email address"). This makes troubleshooting impossible. The Workaround: We bypassed Integration Broker’s outbound target connector for the REST API calls. Instead, we called the Java Apache Commons HttpClient library directly from our PeopleCode Application Classes. Because this library runs directly in the JVM of the application server, we can retrieve both the raw status code and the error response body on failure.

3. Native JSON Array Parser Hallucinations

The Problem: Some developers believe PeopleTools cannot parse top-level JSON arrays (like [{...}, {...}] returned by search endpoints) because older PeopleSoft classes or legacy Campus Community scripts struggled with them. In the past, this led to bizarre workarounds like deploying external web services (such as a Go microservice on Heroku to parse JSON and translate it to XML). The Workaround: We proved this translation step was an unnecessary security risk and performance bottleneck. The native JsonParser class in PeopleTools 8.56+ handles top-level JSON arrays natively. If the root of the JSON payload is an array, you parse it, retrieve the root object, and call GetJsonArray(""):

Local JsonParser &parser = CreateJsonParser();
If &parser.ParseJSONString(&jsonResponse) Then
   Local JsonObject &root = &parser.GetRootObject();
   Local JsonArray &userList = &root.GetJsonArray("");
   /* Loop through users natively */
End-If;

This eliminated the external Heroku dependency entirely, keeping all sensitive user data inside the PeopleSoft security boundary.

4. Plaintext Password Extraction

The Problem: During JIT (Just-in-Time) provisioning, we needed to pass a user’s initial password to Okta. However, PeopleSoft secures the password input on the standard sign-in page, encrypting it before it can be read in Signon PeopleCode. The Workaround: A custom JavaScript hook was added to the signin.html page template to copy the plain text password into a temporary, hidden form field (chgPasswordHidden) right at the moment of submission. This field was read during the sign-on event handler to provision the password to Okta. Important Security Caveat: While this workaround functionalized password sync, it introduces a security risk by exposing plaintext passwords to the browser DOM. A far better, modern approach is to configure Okta as the primary Identity Provider (IdP) and use SAML or OpenID Connect (OIDC) redirection. This allows users to log in directly through Okta, eliminating the need for PeopleSoft to ever touch or store their passwords.

5. Tenant-Wide Rate Limit Risk

The Problem: Okta enforces tenant-wide API rate limits. During a large-scale student registration batch, if PeopleSoft fires API requests too aggressively, it can exhaust the tenant’s API limits. This does not just throttle the provisioning sync—it can block active end-user logins across the entire organization. The Workaround: Our Application Class wrappers read the rate limit headers (X-Rate-Limit-Limit, X-Rate-Limit-Remaining, X-Rate-Limit-Reset) returned in the response of every Apache HttpClient call. If the remaining API quota drops below a safe percentage (e.g., 20%), the code dynamically throttles execution, sleep-waiting in PeopleCode until the reset window clears.

Class Architecture

The provisioning engine is built as a reusable SDK inside a PeopleSoft Application Package (CHG_OKTA):

  • CHG_OKTA:USER:userModel — Represents the Okta user object schema, mapping PeopleSoft fields to JSON properties.
  • CHG_OKTA:USER:userOperations — Implements the API methods using direct Apache HttpClient calls (createUser, updateUser, deactivateUser, getUserByEmail).
  • CHG_OKTA:UTILS:helper — Handles credential decryption and environment checks. It looks up the API keys indexed by database name (%DbName), ensuring a DEV database refreshed from PROD automatically switches to the DEV Okta tenant and API keys.

Observability and Troubleshooting

One of the biggest wins of this architecture was the custom User Provisioning Dashboard page we built. Because we write every transaction state to PS_CHG_OK_REQ_DTL, system administrators do not need access to the Integration Broker Message Monitor to troubleshoot failures.

The dashboard allows admins to:

  • View batch job runs and filter by status (Complete, In-Process, Error).
  • Inspect the exact Okta error message payload returned for a user.
  • Click a “Resubmit” button on a failed row, which marks the status back to NEW to trigger a retry.

Lessons Learned

  1. Bypass the Web Server for High-Volume REST Outbound: If you need full access to HTTP status codes, custom content types (like form URL-encoded), or error response bodies, bypass the standard target connector and use Apache Commons HttpClient directly in PeopleCode.
  2. Never Send Private Data to External Translators: If a native class seems to have a limitation (like parsing JSON arrays), research the native API before building external translator proxies. Native PeopleTools JsonParser is highly capable.
  3. Guard Your Environments: Always index your API keys and endpoints by %DbName in your configuration tables. This prevents refreshed non-production databases from talking to your production cloud tenants.
Author Profile
Chris Malek

Chris Malek is a PeopleTools® Technical Consultant with over two decades of experience. He is available for consulting engagements.

Work with Chris
Subscribe to Updates
SWS Bolt-On
PeopleSoft Simple Web Services

SWS turns SQL into production REST APIs — ready for AI, modern apps, and partner integrations. One install, unlimited potential.

  • Configuration-driven, no coding required
  • JSON, XML, and CSV output
  • Works across all PeopleSoft pillars
  • Built on 25+ years of PeopleSoft expertise
Read More & Purchase
SWS Bolt-On
PeopleSoft Simple Web Services

A powerful PeopleSoft bolt-on that makes REST web services easy. You bring the SQL, SWS handles the rest.

  • Go from idea to production in minutes
  • Zero code migrations after install
  • JSON, XML, and CSV output supported
  • No PeopleCode or Integration Broker expertise required
Read More & Purchase
SWS Bolt-On
PeopleSoft Simple Web Services

Traditional PeopleSoft web services cost $3,600–$13,000 each to develop. SWS deploys production REST APIs in under 5 minutes through configuration alone.

  • No PeopleCode or Integration Broker expertise required
  • Works across Campus Solutions, HCM, and Financials
  • Built-in pagination, caching, and nested data structures
  • Trusted by institutions across higher education and government
Read More & Purchase
SWS Bolt-On
PeopleSoft Simple Web Services

Turn PeopleSoft data into clean REST APIs for AI integrations, modern applications, and vendor data feeds. Configuration-driven — no PeopleCode required.

  • Deploy production APIs in under 5 minutes
  • AI and LLM ready (RAG, chatbots, intelligent search)
  • JSON, XML, and CSV output
  • Zero modifications to delivered PeopleSoft objects
Read More & Purchase
psLens Platform
psLens Operations & Intelligence

Look up any record, field, page, or component, audit security, and monitor Integration Broker across every database — in seconds.

  • 30+ object types browsable
  • 16 real-time alert types
  • Read-only by design
  • No App Designer or SQL required
Learn More
psLens Platform
psLens Operations & Intelligence

A web console built for the PeopleSoft community — operational monitoring, security auditing, and metadata browsing in one tool.

  • Sub-second object search
  • Catch stuck IB messages before users do
  • Audit service permissions from one screen
  • Works in any browser
Learn More
psLens Platform
psLens Operations & Intelligence

On-demand security and operational reports for your PeopleSoft environment — no client install required.

  • 14 on-demand reports
  • Markdown export for AI/LLM workflows
  • No shared tenancy
  • Built on 25+ years of PeopleSoft expertise
Learn More
psLens Platform
psLens Operations & Intelligence

Research any PeopleSoft object and monitor system health from a single browser tab — no App Designer, no SQL.

  • 30+ PeopleSoft object types browsable
  • Real-time alerts before users report problems
  • Read-only and secure
  • Private alpha — early access now
Learn More