Contents

Case Study - PeopleSoft Campus to Workday via RAAS

Overview

A University ran both PeopleSoft Campus Solutions and Workday HCM. The Campus system needs certain employee data (job records, biographical changes, new hires) to be synced from Workday on a regular basis. The team built a reusable integration framework using Workday’s Report as a Service (RAAS) endpoints and PeopleSoft App Engine to automate this data flow.

This case study documents the reusable RAAS Runner framework — a generic App Engine and PeopleCode base class that solved a common integration problem: how to call Workday reports repeatedly, process the XML responses, and keep multiple data flows in sync without writing boilerplate code for each one.

Why This Approach

  • No Workday development required. RAAS automatically exposes any saved Workday report as an HTTP endpoint; no custom APIs, connectors, or Workday Studio configuration needed.
  • Simple firewall setup. PeopleSoft initiates all calls outbound; no inbound firewall rules needed.
  • Incremental polling. Dynamic parameter tokens ({{LASTRUNDATE}}, {{CURRENTDATE_MINUSDAYS_5}}) solve the “what changed” problem — each run only fetches recent changes, keeping response sizes small.
  • Single configuration point. All three integration flows (job data, biographical changes, new hires) use the same framework, keyed by database name to prevent test environments from calling production.

Challenges Working with Workday

  • The consultants did not have any knowlege beyond public documenation and could often NOT provide any infromation when going after specific data fields.
  • The Workday data model is NOT SQL and creating reports that would be trivial in a relational database could be very difficult.
  • One security change on the Workday side (e.g., a permission change that hides a field from the report) could break the entire integration, and debugging was difficult because the RAAS response would still be 200 OK with an empty dataset or just partial data.
  • It is SOAP based. Yuck!

What is Workday RAAS?

RAAS stands for “Report as a Service.” Workday exposes every saved report — both custom and system — as an HTTP endpoint that returns XML, JSON, CSV, or other formats.

URL Patterns

Custom reports:

https://{host}/ccx/service/customreport2/{tenant}/{owner}/{report_name}

System reports:

https://{host}/ccx/service/systemreport2/{tenant}/{report_name}?format=xml

Example: fetching a custom report called “Employee Job Data” from a custom report owner:

GET https://{host}/ccx/service/customreport2/{tenant}/HR_Reports/Employee_Job_Data

Authentication

I chose the Basic Auth approach for simplicity. The Workday user account used for RAAS should be a service account with only the necessary permissions to run the reports.

Response Format

Workday RAAS returns XML with a <wd:Report_Data> root element containing <wd:Report_Entry> rows:

<?xml version="1.0" encoding="UTF-8"?>
<wd:Report_Data>
  <wd:Report_Entry>
    <wd:Employee_ID>12345</wd:Employee_ID>
    <wd:First_Name>Jane</wd:First_Name>
    <wd:Last_Name>Smith</wd:Last_Name>
    <wd:Hire_Date>2023-01-15</wd:Hire_Date>
  </wd:Report_Entry>
  <wd:Report_Entry>
    <wd:Employee_ID>12346</wd:Employee_ID>
    <wd:First_Name>John</wd:First_Name>
    <wd:Last_Name>Doe</wd:Last_Name>
    <wd:Hire_Date>2023-02-01</wd:Hire_Date>
  </wd:Report_Entry>
</wd:Report_Data>

Important Limitation: No Pagination

RAAS returns all matching rows in a single response. There is no pagination or streaming API. For large datasets, this is mitigated by using date-range parameters in the report query string so that each call only retrieves recent changes.


Integration Architecture

  sequenceDiagram

   box "PeopleSoft"
    participant Daemon as Daemon App Engine
    participant Config as Config Table

    participant Processor as Custom Processor
    participant PS as PS Tables
   end
    participant Workday as Workday RAAS
    Daemon->>Config: Poll for active reports
    Config-->>Daemon: Report config + auth URL
    Daemon->>Workday: HTTP GET + Basic Auth
    Workday-->>Daemon: XML Response
    Daemon->>Processor: Invoke processor class
    Processor->>Processor: Parse XML
    Processor->>PS: Write/update records
    PS-->>Processor: Success
    Processor->>Config: Update last run date

The architecture is:

  1. Daemon App Engine (X_WD_RAAS_D) runs continuously on a configurable heartbeat (e.g., every 30 minutes).
  2. Config table (X_WD_RAAS_CONF) defines which reports to run, their parameters, and which PeopleCode processor to use.
  3. RAAS HTTP call uses the config to build a URL, add auth headers, and make a GET request to Workday.
  4. Custom processor parses the XML and processes each row — writing to staging tables, calling Component Interfaces, or other domain logic.
  5. Run log and API log tables record success/failure and raw HTTP details for debugging.

The RAAS Runner Framework

The framework is built on two pieces: a configuration table that defines what to run, and a PeopleCode base class that handles the boilerplate of how to run it.

Configuration Table: PS_X_WD_RAAS_CONF

This table drives the entire integration. Each row defines one report to fetch and process.

Field Purpose
DATABASE_NAME Scopes config to a PS database name (e.g., DEV, TST, PRD). Prevents a test environment from accidentally calling production Workday. Required key.
WD_RAAS_GUID System-generated UUID, primary key. Matches config row to log rows.
REPORT_NAME Workday report name in the format OWNER/REPORT_NAME (no leading slash, no query string). Example: HR_Reports/Employee_Job_Data.
RUN_TIME_PARAMS URL query string parameters as key=value&key=value (no leading ?). Supports dynamic tokens like {{LASTRUNDATE}} (see below).
ACTIVE_YN Boolean. Only active reports are run by the daemon.
FAILURE_NOTIFY_EMAIL Optional. Email address to notify if the report run fails.
OUTPUT_FORMAT XML (recommended), CSV, JSON, or Simple XML. Must match what the Workday report produces.
OUTPUT_DEST_TYPE Either APPCLASS (invoke a PeopleCode processor class) or FILE (write raw response to PS_HOME/datafiles/).
OUTPUT_CLASS_PATH If OUTPUT_DEST_TYPE = APPCLASS, the fully qualified class path (e.g., COMPANY:RAASJOBProcessor).
OUTPUT_FILE_NAME If OUTPUT_DEST_TYPE = FILE, the filename (relative to PS_HOME/datafiles/) to write the raw response to.

Dynamic Parameter Tokens

Query string parameters can include placeholders that are substituted at runtime. This is how you achieve incremental polling without having to update the config each time.

All tokens are case-sensitive.

Token Substitution
{{DATE}} Current date in YYYY-MM-DD format.
{{DATETIME}} Current date and time in YYYY-MM-DD-HHmmss format.
{{LASTRUNDATE}} Last successful run date for this report in YYYY-MM-DD format. On first run, defaults to 30 days ago.
{{LASTRUNDATETIME}} Last successful run date and time in YYYY-MM-DD-HHmmss format.
{{CURRENTDATE_MINUSDAYS_n}} Current date minus n days. Example: {{CURRENTDATE_MINUSDAYS_5}} = 5 days ago.
{{CURRENTDATE_PLUSDAYS_n}} Current date plus n days.
{{LASTRUNDATE_MINUSDAYS_n}} Last successful run date minus n days. Useful for overlap windows.
{{LASTRUNDATETIME_MINUSMINUTES_n}} Last successful run date/time minus n minutes. For high-frequency reports.

Example Configuration

A report to fetch all job changes since the last run:

DATABASE_NAME: PRD
REPORT_NAME: HR_Reports/Employee_Job_Data
RUN_TIME_PARAMS: Entry_Moment_From={{LASTRUNDATE}}&Entry_Moment_To={{DATE}}
OUTPUT_FORMAT: XML
OUTPUT_CLASS_PATH: COMPANY_WORKDAY:RAASJOBProcessor
ACTIVE_YN: Y

A report to fetch future hires in the next 30 days (daily check):

DATABASE_NAME: PRD
REPORT_NAME: HR_Reports/Future_Hires
RUN_TIME_PARAMS: Hire_Date_From={{DATE}}&Hire_Date_To={{CURRENTDATE_PLUSDAYS_30}}
OUTPUT_FORMAT: XML
OUTPUT_CLASS_PATH: COMPANY_WORKDAY:RAASFutureHireProcessor
ACTIVE_YN: Y

Authentication Configuration Table: PS_X_WD_INT_CONF

One separate table stores Workday connection details per environment:

Field Purpose
DATABASE_NAME Key. Scopes auth config to a PS database.
WD_BASE_URL Base URL, e.g., https://wd2-impl-services1.workday.com/ccx/service/.
WD_USERID Workday user ID for Basic Auth, often in the form SERVICE_ACCOUNT@{tenant}.
WD_PASSWORD Encrypted password.

The daemon looks up the appropriate base URL and credentials based on the current database name, ensuring each environment only calls its corresponding Workday tenant.

App Engine Components

Ad-hoc runner (X_WD_RPTRUN):

  • A component that lets developers trigger a specific report on-demand.
  • Used for testing a new report or backfilling missed data.
  • URL: {host}/psp/{database}/EMPLOYEE/SA/c/X_WORKDAY.X_WD_RPTRUN.GBL

Daemon runner (X_WD_RAAS_D):

  • Configured in PeopleTools → Process Scheduler → Daemon Groups.
  • Runs continuously with a configurable heartbeat interval (e.g., every 30 minutes).
  • At each heartbeat, it scans PS_X_WD_RAAS_CONF for all active reports and runs them sequentially.
  • Designed to be lightweight so it can run frequently without consuming resources.

Writing a Custom Processor

The framework separates framework logic (fetching, retrying, logging) from business logic (parsing the XML, deciding what to do with each row). You write a custom processor to implement the business logic.

Processor Class Structure

All processors extend a base class provided by the framework:

import COMPANY_WORKDAY_INTEGRATION:RAASResultProcessor;

class MyRAASProcessor extends COMPANY_WORKDAY_INTEGRATION:RAASResultProcessor
   method processRAASData();
end-class;

method processRAASData
   Local string &rawXML = %Super.RAASData;
   Local Record &config = %Super.RAASRunner;

   /* Parse the XML and process each row. */
   /* Write to PS tables, call Component Interfaces, etc. */

   %super.appendToLog("Processed 42 records");
end-method;

Base Class API

When you extend RAASResultProcessor, you have access to:

Member Type Purpose
%Super.RAASData String The raw XML response from Workday (unparsed). Parse this yourself using PeopleCode’s XML or JSON parsing APIs.
%Super.RAASRunner Record The config table row that triggered this report run. Useful if you need to read custom fields you added to the config.
%super.appendToLog(&msg) Method Writes a message to the run log table. Appears in the admin page and in the log record.
%super.debugLog(&msg) Method Writes a debug message if debug mode is enabled. Useful for verbose output without cluttering the main log.
%super.convertWorkdayDateStringToDate(&wd_date_str) Method Parses a Workday date string (YYYY-MM-DD format) and returns a Date. Handles edge cases like null values.

Example: Processing Job Data

method processRAASData
   Local string &xml = %Super.RAASData;
   Local XmlDoc &doc = CreateXmlDoc(&xml);
   Local XmlNode &root = &doc.DocumentElement;
   Local int &entry_count = 0;

   For &node In &root.ChildNodes
      If &node.NodeName = "wd:Report_Entry" Then
         Local string &emp_id = &node.SelectSingleNode("wd:Employee_ID").Value;
         Local string &hire_date_str = &node.SelectSingleNode("wd:Hire_Date").Value;
         Local date &hire_date = %super.convertWorkdayDateStringToDate(&hire_date_str);

         /* Update or insert PS_X_WD_JOB */
         Local Record &job = CreateRecord(Record.PS_NWDRAAS_JOB);
         &job.EMPLID.Value = &emp_id;
         &job.EMPL_RCD.Value = 0;
         &job.HIRE_DATE.Value = &hire_date;
         &job.InsertOrUpdate();

         &entry_count = &entry_count + 1;
      End-If;
   End-For;

   %super.appendToLog("Processed " | &entry_count | " job records");
end-method;

Reusable Processors from the Framework

The framework includes several pre-built processors for common scenarios:

  • Job Data Processor: Fetches active employee and contractor job records, maintains the job table.
  • Future Hire Processor: Fetches future-dated hires and stages them for search match and validation.
  • Person Demographic Processor: Fetches biographical and demographic changes; syncs via Component Interface.
  • Person Create Processor: Creates new employee records in PeopleSoft Campus using Workday as the source of truth.

For a new integration, you can often start with one of these and extend or clone it rather than writing from scratch.


Logging and Observability

Run Log Table: PS_X_WD_RAAS_LOG

One row per run per report:

Field Purpose
WD_RAAS_GUID FK to config table. Links to the report definition.
RUN_DTTM_START When the run started.
RUN_DTTM_END When the run completed.
ENTRY_COUNT Number of rows processed.
STATUS SUCCESS, ERROR, PARTIAL, SKIPPED.
LOG_MSG Text log messages written by the processor using %super.appendToLog().
ERROR_MSG If status is ERROR, the exception message.

API Log Table: PS_X_WD_API_LOG

Raw HTTP request and response details:

Field Purpose
API_LOG_ID Unique key.
RUN_DTTM When the API call was made.
METHOD HTTP method (GET, POST, etc.).
URL The full URL that was called (with parameters substituted).
REQUEST_BODY For POST calls, the request body. For GET, usually empty.
HTTP_STATUS HTTP response code (200, 401, 404, 500, etc.).
RESPONSE_BODY The raw response. Useful for debugging malformed XML or auth errors.

Admin Page

The framework provides a component where admins can:

  • View the list of configured reports
  • See the most recent run details (start time, entry count, status)
  • View full logs without needing SQL access
  • Trigger an ad-hoc run
  • Enable/disable reports

Navigate to: {host}/psp/{database}/EMPLOYEE/SA/c/X_WORKDAY.X_WD_RAAS_LOG.GBL


Design Decisions

Pull vs. Push

The team chose a pull architecture (PeopleSoft polls Workday) rather than push (Workday sends events to PeopleSoft).

Reasons:

  • Simpler Workday-side setup. No need to configure Workday Business Processes to fire HTTP callbacks.
  • No inbound firewall rules. PeopleSoft initiates all connections; no need to open port 443 inbound to the PeopleSoft gateway.
  • Idempotent. If PeopleSoft crashes or restarts during processing, the next poll simply re-fetches the data. No lost messages.
  • Processing happens in PeopleSoft context. Data transformations and validation run where the data lives.
  • Workday is a HUGE black box that it seems implementation consultants don’t even understand. Building a push integration would require trusting that Workday will reliably send events and handle retries, which felt riskier. For this client, they had more PeopleSoft staff to manage the integration than Workday staff, so it made sense to keep the logic in PeopleSoft.

Trade-off: Data latency. With polling every 30 minutes, job changes in Workday may not appear in PeopleSoft for up to 30 minutes. For most HR use cases, this is acceptable.

RAAS vs. Workday API

RAAS is simpler than Workday’s newer REST APIs.

RAAS advantages:

  • No Workday development required. Any saved report is immediately available as an endpoint.
  • No OAuth token refresh logic needed; Basic Auth works throughout the batch.
  • Minimal Workday tenant configuration.

Workday API advantages:

  • Smaller response payloads (you only request the fields you need).
  • Streaming/pagination support (though RAAS reports can mitigate this with date ranges).
  • Richer query semantics (filters, sorting).

For this integration, the simplicity of RAAS outweighed the API advantages.

Daemon vs. Process Scheduler

Rather than creating a separate Process Scheduler job for each report, the team built a single Daemon that polls the config table and runs all active reports.

Advantages:

  • Single entry point to maintain.
  • Lower operational overhead — one heartbeat check instead of dozens of scheduled jobs.
  • Easier to add new reports — just add a config row; no need to define a new Process Scheduler job.
  • Flexible heartbeat interval — change one number instead of updating job schedules.

Trade-off: All reports run sequentially. If one report takes 10 minutes and the daemon heartbeat is 15 minutes, reports will queue up. Mitigation: tune the heartbeat interval based on the longest-running report, or restructure the daemon to run reports in parallel (more complex).

No Pagination Support

RAAS has no pagination. Reports return all matching rows in one response.

Mitigation: Use date-range parameters in the query string so that each report run only fetches “recent” data.

For example, instead of fetching all job records and diffing against the prior run, the report is configured to fetch job records with an entry date between {{LASTRUNDATE}} and {{DATE}}. Workday indexes these reports on entry date, so filtering at query time is efficient.

First-run caveat: The {{LASTRUNDATE}} token defaults to 30 days ago on first run. If your Workday instance has 2 years of historical data and the report returns 100,000 rows, the first run will be slow. Plan for this — run it during a maintenance window, or manually set LASTRUNDATE in the config to a more recent starting point.

Database Name Scoping

Configuration is scoped by database name (DATABASE_NAME column in config tables). This is a pattern discussed in PeopleSoft as an HTTP Client.

Benefit: Dev and test environments can run the same App Engine code and config setup without any risk of hitting production Workday. The daemon looks up which environment it’s running in, and only calls the corresponding Workday tenant.

Implementation: At startup, the daemon reads DATABASE_NAME from the PeopleTools configuration and uses it as a filter key when querying PS_X_WD_RAAS_CONF and PS_X_WD_INT_CONF.


Lessons Learned

1. Start with a Small Report

Before building the full framework, test with a single, stable Workday report (e.g., a count of active employees). This proves out:

  • Auth works and credentials are correct.
  • The Workday URL format is correct.
  • XML parsing works as expected.
  • The daemon heartbeat interval is reasonable.

Only after validating the basics should you build the processor class library and multi-report scheduling.

2. Tune the LASTRUNDATE Default

The {{LASTRUNDATE}} token defaults to 30 days in the past on the first run. If your Workday instance has a large backlog of historical data, this can cause the first run to fetch 100,000+ rows and take hours.

Mitigation:

  • Either accept the first-run slowness and run it during a planned maintenance window.
  • Or manually query the Workday report in Workday’s UI first, see how much data matches a reasonable date range, and set the LASTRUNDATE in the config to a more recent starting point.

3. Monitor Row Counts

Workday RAAS has no built-in error envelope. A 200 HTTP response with an empty <wd:Report_Data/> is technically valid but may indicate a misconfigured report or broken data extraction.

Best practice: Always log the row count in the processor. If it ever drops to zero unexpectedly, the admin can investigate the Workday report and the raw API log to see what went wrong.

4. Tune the Daemon Heartbeat

The daemon heartbeat interval (how frequently it checks for active reports) should match your data freshness requirements.

  • Hourly or more frequent: For job changes and real-time hiring workflows.
  • Every few hours: For biographical and demographic syncs (data that changes less frequently).
  • Once daily: For archival reports or long-running extracts.

Not all reports need to run at the same frequency. If you need different intervals, the daemon can be extended to read a RUN_INTERVAL_MINUTES column in the config and schedule reports independently.

5. Handle Auth Failures Gracefully

Workday is a multi-tenant SaaS, and credentials can be rotated by the HR team without IT’s knowledge. If the X_WD_INT_CONF table contains expired credentials, every RAAS call will return HTTP 401.

Mitigation:

  • Always include the HTTP status code in the API log.
  • Implement a retry mechanism with exponential backoff (don’t hammer Workday with 401 requests).
  • Send the FAILURE_NOTIFY_EMAIL immediately on the first failure, not after three retries.
  • Provide a manual “test connection” button in the admin page that verifies credentials against a simple Workday endpoint.


Author Info
Chris Malek

Chris Malek is a PeopleTools® Technical Consultant with over two decades of experience working on PeopleSoft enterprise software projects. He is available for consulting engagements.

Work with Chris
PeopleSoft REST APIs in Minutes, Not Months
PeopleSoft Simple Web Services (SWS)

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
Looking for pain-free PeopleSoft web services?
PeopleSoft Simple Web Services (SWS)

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
Stop Building PeopleSoft Web Services the Hard Way
PeopleSoft Simple Web Services (SWS)

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
PeopleSoft REST APIs for AI, Modern Apps, and Integrations
PeopleSoft Simple Web Services (SWS)

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