Workday Authentication: ISU, OAuth 2.0 JWT Bearer & X.509 Certificates
How does Workday authentication work - ISU, OAuth 2.0 JWT Bearer, X.509 certificates?
TL;DR
- Bottom line: Workday uses ISU + password for SOAP Web Services and OAuth 2.0 for REST API; JWT Bearer with X.509 certificates is the recommended server-to-server flow, eliminating stored passwords entirely.
- Key limit: OAuth 2.0 access tokens expire after 60 minutes; refresh tokens can be non-expiring but this violates security best practices — rotate regularly.
- Watch out for: ISU credentials cannot be used directly with the REST API — you must register an OAuth API client and link it to the ISU first.
- Best for: Any system-to-system integration with Workday HCM, Financials, or Payroll APIs where unattended automation is required.
- Authentication: OAuth 2.0 JWT Bearer for REST (server-to-server), ISU + WS-Security for SOAP, X.509 certificates for password-free SOAP authentication.
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.
| Property | Value |
|---|---|
| Vendor | Workday |
| System | Workday HCM / Financials (2025R1, 2025R2) |
| API Surface | REST (JSON) + SOAP Web Services (XML/WS-Security) |
| Current API Version | REST: v1 (stable) / SOAP: per-module versioned WSDLs |
| Editions Covered | All (no edition-gated auth restrictions) |
| Deployment | Cloud (multi-tenant SaaS) |
| API Docs | Workday Community Docs |
| Status | GA |
API Surfaces & Capabilities
| API Surface | Protocol | Auth Method | Best For | Real-time? | Bulk? |
|---|---|---|---|---|---|
| REST API | HTTPS/JSON | OAuth 2.0 (Bearer token) | Modern CRUD, mobile/web apps, custom objects | Yes | Limited |
| SOAP Web Services | HTTPS/XML | ISU + WS-Security password or X.509 cert | Complex transactions, payroll, benefits, full HCM object model | Yes | Via batch operations |
| RaaS (Report as a Service) | HTTPS/JSON or XML | OAuth 2.0 or ISU + WS-Security | Custom report extraction, scheduled data pulls | Yes | Yes (report output) |
| Workday Studio | Internal | ISU-linked | Complex multi-step integrations, EIBs, document transforms | Both | Yes |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Request rate throttle | ~5 req/sec per tenant | REST API (some endpoints) | Soft throttling; no hard published number [src7] |
| Maximum response page size | 100 records (default) | REST API pagination | Use offset and limit parameters [src2] |
| SOAP batch size | 999 records per request | SOAP bulk operations | Split larger payloads [src3] |
| Request body size | ~10 MB | REST API POST/PUT | Large payloads should use EIB [src7] |
Rolling / Daily Limits
Authentication
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| ISU + Password (SOAP) | SOAP Web Services, legacy integrations | Session-based (timeout = 0 for ISU) | N/A | Do not allow UI sessions [src3] |
| OAuth 2.0 Authorization Code | User-context operations, SSO-initiated flows | Access: 60 min; Refresh: configurable | Yes | Requires redirect URL [src5] |
| OAuth 2.0 JWT Bearer | Server-to-server, automated integrations (recommended) | Access: 60 min | No — new JWT per request | Requires X.509 certificate [src4] |
| OAuth 2.0 Client Credentials | System-to-system, simpler setup | Access: 60 min | Yes | Less secure (secret-based) [src5] |
| X.509 Certificate (SOAP) | Password-free SOAP authentication | Session-based | N/A | Upload 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]
- Create the ISU: Search "Create Integration System User". Set username and password (avoid
&,",>). - Disable UI sessions: Check "Do Not Allow UI Sessions".
- Set session timeout to 0: Prevents ISU from timing out during long integrations.
- Exempt from password expiration: Run "Maintain Password Rules" and add the ISU.
- Create Security Group: Choose Unconstrained (full data) or Constrained (subset). Assign the ISU.
- Configure Domain Security Policies: Grant GET/PUT on required domains (Worker Data, Payroll, etc.).
- 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]
- Build JWT with claims:
iss(Client ID),sub(ISU username),aud(token endpoint),exp(max 5 min). - Sign with private key (RS256).
- POST to token endpoint with
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. - Receive access token (60-min lifetime). Use in
Authorization: Bearerheader.
X.509 Certificate Authentication (SOAP WS-Security)
Replaces password-based WS-Security with cryptographic verification. [src6]
- Generate self-signed X.509 cert with OpenSSL (RSA 2048-bit).
- In Workday: "Create x509 Public Key" — paste PEM certificate.
- Assign X.509 to ISU's Web Service Security settings.
- Enable "X509 Token Authentication" checkbox.
- Verify: "View Authentication Policy" shows X509 in Allowed Authentication Types.
Authentication Gotchas
- ISU credentials cannot authenticate REST API directly — register an OAuth API client first. [src1, src3]
- Access tokens are tenant-scoped — a token from tenant A cannot access tenant B. [src2]
- JWT Bearer requires a new JWT for every token request — cache the access token, not the JWT assertion. [src4]
- Non-expiring refresh tokens can still be revoked by admin action, ISU deactivation, or client de-registration. [src5]
- X.509 must be explicitly allowed in Authentication Types — otherwise signed requests are rejected. [src6]
- Session timeout of 0 is ISU-specific — do not set on human accounts. [src3]
Constraints
- REST API requires OAuth 2.0 exclusively — ISU password authentication is only valid for SOAP. REST returns 401 without a Bearer token.
- Each ISU should serve exactly one integration system — sharing ISUs destroys audit trail.
- API client must be linked to an ISU before first use — unlinked clients cannot generate tokens.
- OAuth scopes are grant-time, not request-time — scope is fixed at API client registration.
- X.509 private keys must never be uploaded to Workday — only public certificate goes to Workday.
- Bi-annual releases can change auth behavior — always test in sandbox before R1/R2 cutover.
- PKCE is for public clients only — server-side integrations should use JWT Bearer or Client Credentials.
- ISU password character restrictions —
&,",>are forbidden; no clear error on violation.
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 Method | API Surface | Setup Complexity | Security Level | Password Stored? | Recommended For |
|---|---|---|---|---|---|
| ISU + Password | SOAP only | Low | Medium | Yes (in integration) | Legacy SOAP, quick testing |
| OAuth 2.0 Auth Code | REST | Medium | High | No (token-based) | User-initiated flows, SSO |
| OAuth 2.0 JWT Bearer | REST | High | Highest | No (certificate-based) | Server-to-server production |
| OAuth 2.0 Client Credentials | REST | Medium | High | Client secret in vault | Simpler server-to-server |
| X.509 Certificate | SOAP only | High | Highest | No (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
| Credential | Where Stored | Where Used | Rotation Policy | Gotcha |
|---|---|---|---|---|
| ISU Username | Workday tenant | SOAP WS-Security, OAuth sub claim | Rarely changes | Must include @tenant for SOAP |
| ISU Password | Integration vault | SOAP WS-Security only | Per org policy; ISU exempt | No &, ", > chars |
| Client ID | Workday API Client | JWT iss claim, Basic Auth | Does not change | UUID, auto-generated |
| Client Secret | Integration vault | Basic Auth (non-JWT flows) | Rotate regularly | Not used with JWT Bearer |
| X.509 Public Key | Workday tenant | Linked to API client or ISU | Renew before expiry | Must re-upload on renewal |
| X.509 Private Key | Integration vault (NEVER Workday) | JWT signing, WS-Security signing | Matches public key | Loss = re-register in Workday |
| Refresh Token | Integration vault | Token endpoint (refresh grant) | Rotates on each use | New token per refresh |
| Access Token | In-memory only | Authorization: Bearer header | 60-min auto-expiry | Do not persist to disk |
Data Type Gotchas
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 | Unauthorized | Wrong ISU password, expired token, missing Bearer | Re-authenticate; check credentials |
| 403 | Forbidden | ISU security group lacks domain access | Add permissions, activate policy changes |
| 429 | Too Many Requests | Exceeded throttle or session limit | Exponential backoff (2^n sec, max 5 retries) |
| 400 | Bad Request | Malformed token request, expired JWT | Verify JWT claims (iss, sub, aud, exp) |
| 500 | Internal Server Error | Transient platform error | Retry with backoff; check status page |
| SOAP Fault | SignatureVerificationFailed | Certificate mismatch or corrupted message | Re-verify cert pairing |
Failure Points in Production
- Certificate expiry breaks JWT silently: Workday returns generic 401 with no "expired" detail. Fix:
Set alerts 30 days before cert expiry; automate renewal. [src6] - Refresh token chain breaks on retry: If refresh call fails after Workday issued new token, old token is invalid. Fix:
Persist new refresh token atomically before using access token. [src2] - ISU deactivation cascades to OAuth tokens: Disabling ISU invalidates all linked tokens immediately. Fix:
Separate ISUs per integration; check linked clients before deactivation. [src3] - Security policy not activated: Permissions staged but not live — ISU gets 403 despite correct config. Fix:
Always run "Activate Pending Security Policy Changes". [src3] - Bi-annual release changes auth behavior: R1/R2 can modify scope definitions or WSDL versions. Fix:
Test in sandbox during 4-6 week preview window. [src7] - Multi-tenant confusion: Token from tenant A used against tenant B returns 401. Fix:
Parameterize tenant in all endpoint URLs. [src2]
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
- Forgetting @tenant suffix in SOAP ISU username: SOAP requires
username@tenant— omitting returns ambiguous error. Fix:Always append @tenant_name in SOAP headers. [src3] - Setting non-expiring refresh tokens: Allowed but violates compliance frameworks. Fix:
Set 90-day expiry with automated re-authorization. [src5] - Not activating pending security policy changes: ISU appears configured but gets 403. Fix:
Always run activation task after permission edits. [src3] - Using same certificate for dev and prod: Dev compromise exposes production. Fix:
Separate certificates per environment. [src6] - Hardcoding tenant name in URLs: Breaks multi-environment deployments. Fix:
Store tenant as environment variable. [src2] - Ignoring bi-annual release auth impact: Scope definitions can change with R1/R2. Fix:
Test in sandbox during preview window. [src7]
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
| Release | Date | Status | Auth Changes | Migration Notes |
|---|---|---|---|---|
| 2025R2 | 2025-09 | Current | OAuth scope refinements | Review scope assignments |
| 2025R1 | 2025-03 | Supported | Minor WSDL updates | No breaking auth changes |
| 2024R2 | 2024-09 | Supported | OAuth 2.0 scope model updates | May require API client re-registration |
| 2024R1 | 2024-03 | EOL (approaching) | PKCE support added | Optional — 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 When | Don't Use When | Use Instead |
|---|---|---|
| Automated server-to-server integration with Workday | One-time manual data extract | Workday Report Writer or CSV export |
| Custom integration reading/writing worker or financial data | Using pre-built iPaaS connector (Workato, MuleSoft) | iPaaS handles auth internally |
| Password-free auth for compliance (SOC 2, ISO 27001) | Testing or prototyping only | ISU + Password for quick testing |
| Multi-tenant SaaS connecting to customer Workday tenants | Single-use script that runs once | Simple ISU + Password |
| Fine-grained audit trail per integration | Integration within Workday Studio | Studio manages its own ISU context |
Important Caveats
- Workday does not publish comprehensive rate limits — ~5 req/sec is observed, not contractual. Actual limits vary by tenant size.
- OAuth scopes map to functional areas (Staffing, Payroll), not individual endpoints — granting a scope gives access to all endpoints in that area.
- Sandbox and production tenants are separate environments with separate ISUs, API clients, and certificates — no cross-environment token portability.
- Workday's bi-annual release cycle means auth behavior can change twice per year — validate in sandbox during preview window.
- This card covers authentication patterns only — for endpoint reference, WSDL details, or data model docs, see official Workday Community documentation (requires login).