Workday Authentication: ISU, OAuth 2.0 JWT Bearer & X.509 Certificates

Type: ERP Integration System: Workday HCM / Financials (2025R1/R2) Confidence: 0.88 Sources: 7 Verified: 2026-03-02 Freshness: evolving

TL;DR

System Profile

Workday is a cloud-only SaaS platform providing HCM, Financials, Payroll, and Planning modules. All tenants receive the same software release twice per year (R1 and R2). Authentication methods apply uniformly across all editions. This card covers all three authentication paths: ISU with password for SOAP, OAuth 2.0 (Authorization Code, JWT Bearer, Client Credentials) for REST, and X.509 certificate-based WS-Security for password-free SOAP.

PropertyValue
VendorWorkday
SystemWorkday HCM / Financials (2025R1, 2025R2)
API SurfaceREST (JSON) + SOAP Web Services (XML/WS-Security)
Current API VersionREST: v1 (stable) / SOAP: per-module versioned WSDLs
Editions CoveredAll (no edition-gated auth restrictions)
DeploymentCloud (multi-tenant SaaS)
API DocsWorkday Community Docs
StatusGA

API Surfaces & Capabilities

API SurfaceProtocolAuth MethodBest ForReal-time?Bulk?
REST APIHTTPS/JSONOAuth 2.0 (Bearer token)Modern CRUD, mobile/web apps, custom objectsYesLimited
SOAP Web ServicesHTTPS/XMLISU + WS-Security password or X.509 certComplex transactions, payroll, benefits, full HCM object modelYesVia batch operations
RaaS (Report as a Service)HTTPS/JSON or XMLOAuth 2.0 or ISU + WS-SecurityCustom report extraction, scheduled data pullsYesYes (report output)
Workday StudioInternalISU-linkedComplex multi-step integrations, EIBs, document transformsBothYes

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueApplies ToNotes
Request rate throttle~5 req/sec per tenantREST API (some endpoints)Soft throttling; no hard published number [src7]
Maximum response page size100 records (default)REST API paginationUse offset and limit parameters [src2]
SOAP batch size999 records per requestSOAP bulk operationsSplit larger payloads [src3]
Request body size~10 MBREST API POST/PUTLarge payloads should use EIB [src7]

Rolling / Daily Limits

Limit TypeValueWindowNotes
Concurrent API sessionsTenant-dependentPer tenantExcess sessions get HTTP 429 [src7]
Token refresh rateNo explicit limitPer clientRapid cycling may trigger security alerts [src2]
Report executionQueue-basedPer tenantLarge RaaS reports can block other report jobs [src3]

Authentication

FlowUse WhenToken LifetimeRefresh?Notes
ISU + Password (SOAP)SOAP Web Services, legacy integrationsSession-based (timeout = 0 for ISU)N/ADo not allow UI sessions [src3]
OAuth 2.0 Authorization CodeUser-context operations, SSO-initiated flowsAccess: 60 min; Refresh: configurableYesRequires redirect URL [src5]
OAuth 2.0 JWT BearerServer-to-server, automated integrations (recommended)Access: 60 minNo — new JWT per requestRequires X.509 certificate [src4]
OAuth 2.0 Client CredentialsSystem-to-system, simpler setupAccess: 60 minYesLess secure (secret-based) [src5]
X.509 Certificate (SOAP)Password-free SOAP authenticationSession-basedN/AUpload public key to Workday [src6]

ISU (Integration System User) Setup

An ISU is a dedicated service account for system-to-system integrations. It is the foundation for both SOAP and OAuth flows. [src3]

  1. Create the ISU: Search "Create Integration System User". Set username and password (avoid &, ", >).
  2. Disable UI sessions: Check "Do Not Allow UI Sessions".
  3. Set session timeout to 0: Prevents ISU from timing out during long integrations.
  4. Exempt from password expiration: Run "Maintain Password Rules" and add the ISU.
  5. Create Security Group: Choose Unconstrained (full data) or Constrained (subset). Assign the ISU.
  6. Configure Domain Security Policies: Grant GET/PUT on required domains (Worker Data, Payroll, etc.).
  7. Activate: Run "Activate Pending Security Policy Changes".

OAuth 2.0 JWT Bearer Flow (Recommended for Server-to-Server)

Eliminates stored secrets by using a signed JWT assertion. [src4]

  1. Build JWT with claims: iss (Client ID), sub (ISU username), aud (token endpoint), exp (max 5 min).
  2. Sign with private key (RS256).
  3. POST to token endpoint with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer.
  4. Receive access token (60-min lifetime). Use in Authorization: Bearer header.

X.509 Certificate Authentication (SOAP WS-Security)

Replaces password-based WS-Security with cryptographic verification. [src6]

  1. Generate self-signed X.509 cert with OpenSSL (RSA 2048-bit).
  2. In Workday: "Create x509 Public Key" — paste PEM certificate.
  3. Assign X.509 to ISU's Web Service Security settings.
  4. Enable "X509 Token Authentication" checkbox.
  5. Verify: "View Authentication Policy" shows X509 in Allowed Authentication Types.

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START — Authenticate with Workday
├── Which API surface?
│   ├── REST API (JSON, modern)
│   │   ├── Server-to-server (no human in loop)?
│   │   │   ├── YES → OAuth 2.0 JWT Bearer (recommended)
│   │   │   │   ├── Have X.509 certificate? → Proceed with JWT Bearer
│   │   │   │   └── No certificate yet → Generate self-signed for dev, CA-signed for prod
│   │   │   └── Prefer simpler setup? → OAuth 2.0 Client Credentials (less secure)
│   │   └── User-context (SSO, interactive)?
│   │       └── OAuth 2.0 Authorization Code (+PKCE for public clients)
│   ├── SOAP Web Services (XML, legacy/complex)
│   │   ├── Need password-free auth?
│   │   │   ├── YES → X.509 Certificate + WS-Security
│   │   │   └── NO → ISU + Password + WS-Security
│   │   └── Complex multi-step integration?
│   │       └── Workday Studio (uses ISU internally)
│   └── RaaS (Report as a Service)
│       ├── REST endpoint? → OAuth 2.0 (same as REST)
│       └── SOAP endpoint? → ISU or X.509 (same as SOAP)
├── Security requirements?
│   ├── Highest (no stored secrets) → JWT Bearer or X.509
│   ├── Standard (secret in vault) → Client Credentials
│   └── Legacy/testing only → ISU + Password
└── Token management?
    ├── Long-running batch → Cache access token, refresh before 60 min
    ├── Frequent short calls → New token per batch, not per call
    └── Multi-tenant → Separate API client per tenant

Quick Reference

Auth MethodAPI SurfaceSetup ComplexitySecurity LevelPassword Stored?Recommended For
ISU + PasswordSOAP onlyLowMediumYes (in integration)Legacy SOAP, quick testing
OAuth 2.0 Auth CodeRESTMediumHighNo (token-based)User-initiated flows, SSO
OAuth 2.0 JWT BearerRESTHighHighestNo (certificate-based)Server-to-server production
OAuth 2.0 Client CredentialsRESTMediumHighClient secret in vaultSimpler server-to-server
X.509 CertificateSOAP onlyHighHighestNo (certificate-based)Password-free SOAP, high security

Step-by-Step Integration Guide

1. Create the Integration System User (ISU)

Every Workday integration starts with an ISU — the service account identity. [src3]

Workday Task: "Create Integration System User"

Fields:
  User Name:           myapp_isu_prod
  Password:            <strong random, no &, ", or > chars>
  Session Timeout:     0  (never timeout)
  Do Not Allow UI:     checked
  Require New Password: unchecked (for ISU)

Verify: Search "View Integration System User" — confirm "Do Not Allow UI Sessions" = Yes.

2. Create Security Group and Assign Domain Permissions

ISU has no permissions until assigned to a security group with domain policies. [src3]

Workday Task: "Create Security Group"
Type: Integration System Security Group (Unconstrained)
Name: ISG_MyApp_Prod
Members: myapp_isu_prod

Workday Task: "Edit Domain Security Policy Permissions"
Domain: Worker Data: Public Worker Reports
Security Group: ISG_MyApp_Prod
Permissions: Get (read)

Workday Task: "Activate Pending Security Policy Changes"

Verify: Run "View Security Group Membership for Integration System".

3. Register an OAuth 2.0 API Client

Required for any REST API access. [src5]

Workday Task: "Register API Client for Integrations"
Client Name:   MyApp REST Client
Grant Type:    Jwt Bearer Grant
X509 Cert:     Upload public key
Scope:         Staffing, Human Resources, System
ISU:           myapp_isu_prod

Record: Client ID + Token Endpoint

Verify: Search "View API Clients" — confirm grant type and linked ISU.

4. Generate X.509 Certificate

For JWT Bearer flow — public key to Workday, private key in your vault. [src4, src6]

# Generate 2048-bit RSA private key
openssl genrsa -out workday_private.pem 2048

# Generate self-signed X.509 certificate (365 days)
openssl req -new -x509 -key workday_private.pem \
  -out workday_cert.pem -days 365 \
  -subj "/CN=MyApp Workday Integration/O=MyCompany"

Verify: openssl x509 -in workday_cert.pem -noout -dates

5. Request Access Token via JWT Bearer

Build and sign a JWT, exchange for access token. [src4]

import jwt, time, requests

CLIENT_ID = "your-client-id-uuid"
ISU_USERNAME = "myapp_isu_prod"
TOKEN_ENDPOINT = "https://wd5-impl-services1.workday.com/ccx/oauth2/your_tenant/token"

with open("workday_private.pem", "r") as f:
    private_key = f.read()

now = int(time.time())
signed_jwt = jwt.encode({
    "iss": CLIENT_ID, "sub": ISU_USERNAME,
    "aud": TOKEN_ENDPOINT,
    "iat": now, "exp": now + 300,
}, private_key, algorithm="RS256")

resp = requests.post(TOKEN_ENDPOINT, data={
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": signed_jwt,
})
access_token = resp.json()["access_token"]

Verify: resp.status_code == 200 and non-empty access_token.

6. Make Authenticated REST API Call

Use the Bearer token to call Workday endpoints. [src2]

curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://wd5-impl-services1.workday.com/ccx/api/v1/your_tenant/workers?limit=5"

Verify: HTTP 200 with JSON array of worker records.

7. SOAP Authentication with ISU + WS-Security

For SOAP integrations, embed ISU credentials in the WS-Security header. [src3]

<soapenv:Header>
  <wsse:Security xmlns:wsse="...">
    <wsse:UsernameToken>
      <wsse:Username>myapp_isu_prod@your_tenant</wsse:Username>
      <wsse:Password Type="...#PasswordText">password</wsse:Password>
    </wsse:UsernameToken>
  </wsse:Security>
</soapenv:Header>

Verify: SOAP response contains expected data element.

Code Examples

Python: OAuth 2.0 JWT Bearer with Token Caching

# Input:  Workday tenant config (client_id, private_key, isu, endpoint)
# Output: Valid access token or raises error

import jwt, time, requests
from typing import Optional

class WorkdayAuth:
    def __init__(self, client_id, isu_username, private_key_path, token_endpoint):
        self.client_id = client_id
        self.isu_username = isu_username
        self.token_endpoint = token_endpoint
        with open(private_key_path) as f:
            self.private_key = f.read()
        self._token: Optional[str] = None
        self._token_expiry: float = 0

    def get_token(self) -> str:
        if self._token and time.time() < self._token_expiry - 120:
            return self._token
        return self._refresh_token()

    def _refresh_token(self, retries=3) -> str:
        now = int(time.time())
        assertion = jwt.encode({
            "iss": self.client_id, "sub": self.isu_username,
            "aud": self.token_endpoint, "iat": now, "exp": now + 300,
        }, self.private_key, algorithm="RS256")
        for attempt in range(retries):
            resp = requests.post(self.token_endpoint, data={
                "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
                "assertion": assertion,
            }, timeout=30)
            if resp.status_code == 200:
                self._token = resp.json()["access_token"]
                self._token_expiry = now + 3600
                return self._token
            if resp.status_code == 429:
                time.sleep(2 ** attempt)
                continue
            resp.raise_for_status()
        raise RuntimeError("Failed to obtain Workday token")

JavaScript/Node.js: Refresh Token Flow

// Input:  client_id, client_secret, refresh_token, token_endpoint
// Output: { access_token, refresh_token } object

import fetch from 'node-fetch';  // [email protected]

async function getWorkdayAccessToken({ clientId, clientSecret, refreshToken, tokenEndpoint }) {
  const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
  const resp = await fetch(tokenEndpoint, {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${credentials}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
    }),
  });
  if (!resp.ok) throw new Error(`Workday auth failed: ${resp.status}`);
  return resp.json();  // { access_token, token_type, refresh_token }
}

cURL: Test Refresh Token Flow

# Input:  Client ID, Client Secret, Refresh Token
# Output: JSON { access_token, token_type, refresh_token }

curl -X POST "https://wd5-impl-services1.workday.com/ccx/oauth2/your_tenant/token" \
  -u "your_client_id:your_client_secret" \
  -d "grant_type=refresh_token&refresh_token=your_refresh_token" \
  -H "Content-Type: application/x-www-form-urlencoded"

Data Mapping

Authentication Credential Mapping

CredentialWhere StoredWhere UsedRotation PolicyGotcha
ISU UsernameWorkday tenantSOAP WS-Security, OAuth sub claimRarely changesMust include @tenant for SOAP
ISU PasswordIntegration vaultSOAP WS-Security onlyPer org policy; ISU exemptNo &, ", > chars
Client IDWorkday API ClientJWT iss claim, Basic AuthDoes not changeUUID, auto-generated
Client SecretIntegration vaultBasic Auth (non-JWT flows)Rotate regularlyNot used with JWT Bearer
X.509 Public KeyWorkday tenantLinked to API client or ISURenew before expiryMust re-upload on renewal
X.509 Private KeyIntegration vault (NEVER Workday)JWT signing, WS-Security signingMatches public keyLoss = re-register in Workday
Refresh TokenIntegration vaultToken endpoint (refresh grant)Rotates on each useNew token per refresh
Access TokenIn-memory onlyAuthorization: Bearer header60-min auto-expiryDo not persist to disk

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
401UnauthorizedWrong ISU password, expired token, missing BearerRe-authenticate; check credentials
403ForbiddenISU security group lacks domain accessAdd permissions, activate policy changes
429Too Many RequestsExceeded throttle or session limitExponential backoff (2^n sec, max 5 retries)
400Bad RequestMalformed token request, expired JWTVerify JWT claims (iss, sub, aud, exp)
500Internal Server ErrorTransient platform errorRetry with backoff; check status page
SOAP FaultSignatureVerificationFailedCertificate mismatch or corrupted messageRe-verify cert pairing

Failure Points in Production

Anti-Patterns

Wrong: Using ISU password for REST API

# BAD — ISU password cannot authenticate REST API; returns 401
resp = requests.get(url, auth=("isu@tenant", "password"))

Correct: Use OAuth 2.0 JWT Bearer for REST

# GOOD — Token-based, no stored password, 60-min expiry
resp = requests.get(url, headers={"Authorization": f"Bearer {token}"})

Wrong: Sharing one ISU across multiple integrations

# BAD — Audit log shows same user for everything
ISU: shared_user → Payroll + Benefits + Recruiting + Reports

Correct: Dedicated ISU per integration

# GOOD — Clear audit trail, isolated failure domain
ISU: payroll_isu → Payroll only
ISU: benefits_isu → Benefits only
ISU: recruiting_isu → Recruiting only

Wrong: Caching refresh tokens without atomic replacement

# BAD — Crash between refresh and persist loses token chain
new_tokens = refresh(old_token)
do_work(new_tokens["access_token"])  # might crash
save(new_tokens["refresh_token"])     # never reached

Correct: Persist new refresh token before using access token

# GOOD — Atomic rotation: persist first, use second
new_tokens = refresh(old_token)
save(new_tokens["refresh_token"])     # persist FIRST
do_work(new_tokens["access_token"])   # safe to fail

Common Pitfalls

Diagnostic Commands

# Test OAuth 2.0 token endpoint connectivity
curl -v -X POST "https://wd5-impl-services1.workday.com/ccx/oauth2/your_tenant/token" \
  -d "grant_type=refresh_token&refresh_token=test" \
  -u "client_id:client_secret" 2>&1 | head -30

# Verify X.509 certificate details and expiration
openssl x509 -in workday_cert.pem -noout -subject -dates -issuer

# Check certificate and private key match
openssl x509 -in workday_cert.pem -noout -modulus | openssl md5
openssl rsa -in workday_private.pem -noout -modulus | openssl md5

# Verify REST API access with Bearer token
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer your_access_token" \
  "https://wd5-impl-services1.workday.com/ccx/api/v1/your_tenant/workers?limit=1"

# Generate new self-signed X.509 certificate
openssl req -new -x509 -key workday_private.pem -out workday_cert_new.pem \
  -days 365 -subj "/CN=MyApp Workday/O=MyCompany"

Version History & Compatibility

ReleaseDateStatusAuth ChangesMigration Notes
2025R22025-09CurrentOAuth scope refinementsReview scope assignments
2025R12025-03SupportedMinor WSDL updatesNo breaking auth changes
2024R22024-09SupportedOAuth 2.0 scope model updatesMay require API client re-registration
2024R12024-03EOL (approaching)PKCE support addedOptional — existing flows unaffected

Deprecation Policy

Workday supports SOAP API versions for approximately 2-3 years. REST API v1 is stable and not versioned the same way — breaking changes are rare and announced via Workday Community. Always test in sandbox during the 4-6 week preview window before each R1/R2 rollout. [src7]

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Automated server-to-server integration with WorkdayOne-time manual data extractWorkday Report Writer or CSV export
Custom integration reading/writing worker or financial dataUsing pre-built iPaaS connector (Workato, MuleSoft)iPaaS handles auth internally
Password-free auth for compliance (SOC 2, ISO 27001)Testing or prototyping onlyISU + Password for quick testing
Multi-tenant SaaS connecting to customer Workday tenantsSingle-use script that runs onceSimple ISU + Password
Fine-grained audit trail per integrationIntegration within Workday StudioStudio manages its own ISU context

Important Caveats

Related Units