---
# === IDENTITY ===
id: business/erp-integration/workday-authentication-isu-oauth/2026
canonical_question: "How does Workday authentication work - ISU, OAuth 2.0 JWT Bearer, X.509 certificates?"
aliases:
  - "Workday Integration System User authentication setup"
  - "Workday OAuth 2.0 JWT Bearer token flow"
  - "Workday X.509 certificate WS-Security authentication"
  - "How to authenticate with Workday API using ISU or OAuth"
entity_type: erp_integration
domain: business > erp-integration > workday-authentication-isu-oauth
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Workday HCM / Financials"
    vendor: "Workday"
    version: "2025R1 / 2025R2"
    edition: "All editions (Enterprise, Standard)"
    deployment: "cloud"
    api_surface: "REST, SOAP (WS-Security)"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.88
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "2024R2 — OAuth 2.0 scope model updates"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "ISU credentials are SOAP-only — REST API requires OAuth 2.0 tokens, never raw ISU password"
  - "Each ISU should be restricted to a single integration system for auditability"
  - "OAuth 2.0 access tokens expire after 60 minutes; refresh tokens can be configured non-expiring but this is not recommended"
  - "JWT Bearer grant requires X.509 certificate registration in Workday — self-signed accepted for dev, CA-signed recommended for production"
  - "API client must be explicitly linked to an ISU before that ISU can use OAuth — unlinked ISUs cannot authenticate via REST"
  - "Workday enforces throttling (~5 requests/second per tenant on some endpoints); no published hard daily limit"
  - "X.509 certificate authentication must be explicitly enabled in the Allowed Authentication Types for the ISU"
  - "Workday releases updates twice per year (R1, R2) — schema and auth changes require sandbox testing before production cutover"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs Workday REST API endpoint reference and rate limits, not authentication"
    use_instead: "business/erp-integration/workday-rest-api-capabilities/2026"
  - condition: "User needs SOAP API request structure and WSDL reference"
    use_instead: "business/erp-integration/workday-soap-api-capabilities/2026"
  - condition: "User needs Workday Studio integration development"
    use_instead: "business/erp-integration/workday-studio-integration/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: api_surface
    question: "Which Workday API surface are you using?"
    type: choice
    options:
      - "REST API (JSON, modern integrations)"
      - "SOAP Web Services (XML, legacy/complex transactions)"
      - "Both REST and SOAP"
  - key: auth_method
    question: "Which authentication method do you need?"
    type: choice
    options:
      - "ISU + password (SOAP WS-Security)"
      - "OAuth 2.0 Authorization Code (user-context)"
      - "OAuth 2.0 JWT Bearer (server-to-server, no user interaction)"
      - "OAuth 2.0 Client Credentials"
      - "X.509 certificate (SOAP WS-Security, no password)"
  - key: direction
    question: "What's the data flow direction?"
    type: choice
    options:
      - "inbound (writing to Workday)"
      - "outbound (reading from Workday)"
      - "bidirectional sync"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/workday-authentication-isu-oauth/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-02)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/oracle-erp-cloud-authentication/2026"
      label: "Oracle ERP Cloud Authentication — comparable OAuth + certificate patterns"
  solves: []
  alternative_to:
    - id: "business/erp-integration/netsuite-tba-vs-oauth2/2026"
      label: "NetSuite TBA vs OAuth 2.0 — alternative ERP auth comparison"
  often_confused_with: []

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Workday REST API Authentication"
    author: Upptalk
    url: https://upptalk.com/workday-rest-api-authentication/
    type: technical_blog
    published: 2025-06-15
    reliability: moderate_high
  - id: src2
    title: "Complete Guide to Workday REST API Integration and Security"
    author: Reco.ai
    url: https://www.reco.ai/hub/workday-rest-api-integration-security
    type: technical_blog
    published: 2025-09-20
    reliability: moderate_high
  - id: src3
    title: "Workday API Integration Guide (In-Depth)"
    author: Knit
    url: https://www.getknit.dev/blog/workday-api-integration-in-depth
    type: technical_blog
    published: 2025-07-10
    reliability: moderate_high
  - id: src4
    title: "Setting up OAuth JWT authentication for Workday"
    author: Qlik / Talend
    url: https://help.qlik.com/talend/en-US/components/8.0/workday/settingup-workday-oauth-jwt-authentication
    type: technical_blog
    published: 2025-04-01
    reliability: high
  - id: src5
    title: "Registering API Clients in Workday"
    author: Upptalk
    url: https://upptalk.com/registering-api-clients-in-workday-simplifying-integrations-with-secure-authentication/
    type: technical_blog
    published: 2025-08-10
    reliability: moderate_high
  - id: src6
    title: "Workday Integration X509 Certificate-based Auth"
    author: Beamery
    url: https://support.beamery.com/hc/en-us/articles/21861438657297-Workday-Integration-X509-Certificate-based-Auth
    type: technical_blog
    published: 2025-01-15
    reliability: moderate_high
  - id: src7
    title: "Mastering Workday REST API Integration: The Complete 2025 Guide"
    author: Sama Integrations
    url: https://samaintegrations.com/mastering-workday-rest-api-integration-the-complete-2025-guide-for-businesses/
    type: technical_blog
    published: 2025-10-05
    reliability: moderate_high
---

# Workday Authentication: 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 — there are no edition-gated API restrictions. This card covers all three authentication paths: ISU (Integration System User) 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. Workday Studio integrations and RaaS (Report as a Service) also use these authentication methods.

| 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 URL** | [Workday Community Docs](https://community.workday.com) |
| **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) | Workday enforces soft throttling; no hard published number [src7] |
| Maximum response page size | 100 records (default) | REST API pagination | Use `offset` and `limit` parameters for paging [src2] |
| SOAP batch size | 999 records per request | SOAP bulk operations | Split larger payloads into multiple requests [src3] |
| Request body size | ~10 MB | REST API POST/PUT | Varies by endpoint; large payloads should use EIB [src7] |

### Rolling / Daily Limits

| Limit Type | Value | Window | Notes |
|---|---|---|---|
| Concurrent API sessions | Tenant-dependent | Per tenant | Workday does not publish a hard number; excess sessions get HTTP 429 [src7] |
| Token refresh rate | No explicit limit | Per client | But rapid token cycling may trigger security alerts [src2] |
| Report execution | Queue-based | Per tenant | Large RaaS reports can block other report jobs in the same tenant [src3] |

## Authentication

| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| ISU + Password (SOAP) | SOAP Web Services, legacy integrations | Session-based (set timeout to 0 for ISU) | N/A — session-based | Do not allow UI sessions for ISU accounts [src3] |
| OAuth 2.0 Authorization Code | User-context operations, SSO-initiated flows | Access: 60 min; Refresh: configurable | Yes — refresh token | Requires redirect URL; human in the loop [src5] |
| OAuth 2.0 JWT Bearer | Server-to-server, automated integrations (recommended) | Access: 60 min | No refresh — generate new JWT per token request | Requires X.509 certificate + API client registration [src4] |
| OAuth 2.0 Client Credentials | System-to-system where ISU context not needed | Access: 60 min | Yes — refresh token | Simpler than JWT but less secure (secret-based) [src5] |
| X.509 Certificate (SOAP) | Password-free SOAP authentication, highest security | Session-based | N/A — certificate-based | Upload public key to Workday; sign requests with private key [src6] |

### ISU (Integration System User) Setup

An ISU is a dedicated service account — a "robot login" — for system-to-system integrations. It is the foundation for both SOAP and OAuth flows. [src3]

**Step-by-step creation:**

1. **Create the ISU**: Search "Create Integration System User" in Workday. Set username and password (avoid `&`, `"`, `>` in password). [src3]
2. **Disable UI sessions**: Check "Do Not Allow UI Sessions" to prevent interactive login. [src3]
3. **Set session timeout to 0**: Prevents the ISU from timing out during long-running integrations. [src3]
4. **Exempt from password expiration**: Run "Maintain Password Rules" and add the ISU to the exempt list. [src3]
5. **Create a Security Group**: Search "Create Security Group" — choose "Integration System Security Group (Unconstrained)" for full data access or "(Constrained)" for a subset. Assign the ISU. [src3]
6. **Configure Domain Security Policies**: Grant the security group GET/PUT permissions on required domains (Worker Data, Payroll, Benefits, etc.). [src3]
7. **Activate security changes**: Run "Activate Pending Security Policy Changes" to apply. [src3]

### OAuth 2.0 API Client Registration

Before using any OAuth flow, you must register an API client in Workday. [src5]

1. Navigate to **Setup > Administration > Security** or search "Register API Client for Integrations". [src5]
2. Ensure **OAuth 2.0 Clients Enabled** is checked in tenant security configuration. [src5]
3. Set **Client Name** (descriptive identifier for your integration). [src5]
4. Select **Grant Type**: Authorization Code, JWT Bearer, or Client Credentials. [src5]
5. For JWT Bearer: select "Jwt Bearer Grant" and upload X.509 certificate via "Create x509 Public Key". [src4]
6. Configure **Scopes**: Select functional areas (Staffing, Payroll, Human Resources, Contact Information, etc.). [src5]
7. Link the API client to a specific **ISU** — this ISU's permissions determine what the OAuth token can access. [src1]
8. Save. Record the **Client ID** (and Client Secret for non-JWT flows). Record the **Token Endpoint**: `https://<workday-host>/ccx/oauth2/<tenant>/token`. [src2]

### OAuth 2.0 JWT Bearer Flow (Recommended for Server-to-Server)

The JWT Bearer flow eliminates stored secrets by using a signed JWT assertion instead of a client secret. [src4]

**Prerequisites**: Registered API client with JWT Bearer grant, X.509 certificate (public key uploaded to Workday, private key held by integration).

**Token request flow:**

1. Build a JWT with claims: `iss` (Client ID), `sub` (ISU username), `aud` (token endpoint URL), `exp` (expiration, max 5 min from now).
2. Sign the JWT with your private key (RS256).
3. POST to token endpoint with `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer` and `assertion=<signed_jwt>`.
4. Receive access token (60-min lifetime). Use in `Authorization: Bearer <token>` header. [src4]

### X.509 Certificate Authentication (SOAP WS-Security)

X.509 certificate authentication replaces password-based WS-Security with cryptographic certificate verification. [src6]

1. **Generate a certificate**: Use OpenSSL to create a self-signed X.509 cert and RSA 2048-bit key. [src6]
2. In Workday: search "Create x509 Public Key". Paste the PEM-encoded public certificate. Name it descriptively. [src6]
3. Assign the X.509 public key to the ISU's Web Service Security settings. [src6]
4. Enable "X509 Token Authentication" checkbox for the ISU. [src6]
5. Verify: Run "View Authentication Policy" report and confirm X509 appears in "Allowed Authentication Types". [src6]
6. In your integration: sign SOAP requests with the private key using WS-Security BinarySecurityToken. [src6]

### Authentication Gotchas

- **ISU cannot authenticate REST API directly** — you must register an OAuth API client and link it to the ISU. Passing ISU credentials to a REST endpoint returns 401. [src1, src3]
- **OAuth 2.0 access tokens are tenant-scoped** — a token from tenant A cannot access tenant B, even with the same API client. Always parameterize tenant in your token endpoint URL. [src2]
- **JWT Bearer flow requires a new JWT for every token request** — do not cache the JWT assertion itself; only cache the resulting access token (up to 60 minutes). [src4]
- **Refresh tokens marked non-expiring can still be revoked** — admin revocation, ISU deactivation, or API client de-registration invalidates all tokens immediately. Build reconnection logic. [src5]
- **X.509 certificate authentication must be explicitly allowed** — if "Allowed Authentication Types" for the ISU does not include X509, signed SOAP requests will be rejected even with a valid certificate. [src6]
- **Session timeout of 0 is ISU-specific** — do not set this on human user accounts; it would keep sessions open indefinitely. [src3]

## Constraints
<!-- Agents: read this section before recommending any integration approach from this unit.
     These are hard constraints that must not be violated. -->

- **REST API requires OAuth 2.0 exclusively** — ISU password authentication is only valid for SOAP Web Services. Any REST endpoint returns 401 without a valid Bearer token. [src1]
- **Each ISU should serve exactly one integration system** — sharing ISUs across integrations makes audit logs useless and violates Workday security best practices. [src3]
- **API client must be linked to an ISU before first use** — unlinked API clients cannot generate tokens. This is a registration-time configuration, not a runtime setting. [src5]
- **OAuth scopes are grant-time, not request-time** — you cannot request a narrower scope at token refresh; the scope is fixed when the API client is registered. [src5]
- **X.509 private keys must never be uploaded to Workday** — only the public certificate goes into Workday; the private key stays in your integration platform's secure vault. [src6]
- **Workday bi-annual releases can change authentication behavior** — always test auth flows in a sandbox tenant before production R1/R2 cutover. [src7]
- **PKCE is available for public clients only** — if your integration runs server-side, use JWT Bearer or Client Credentials, not Authorization Code with PKCE. [src5]
- **Password characters restricted for ISU** — `&`, `"`, and `>` characters are forbidden in ISU passwords and will cause authentication failures with no clear error message. [src3]

## 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 (simpler setup)
│   │   └── Complex multi-step integration?
│   │       └── Workday Studio (uses ISU internally)
│   └── RaaS (Report as a Service)
│       ├── Calling via REST endpoint? → OAuth 2.0 (same as REST above)
│       └── Calling via SOAP endpoint? → ISU or X.509 (same as SOAP above)
├── Security requirements?
│   ├── Highest (no stored secrets) → JWT Bearer or X.509 Certificate
│   ├── 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 → Generate new token per batch, not per call
    └── Multi-tenant → Separate API client per tenant
```

## Quick Reference

| Authentication 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 contexts |
| OAuth 2.0 JWT Bearer | REST | High | Highest | No (certificate-based) | Server-to-server production integrations |
| OAuth 2.0 Client Credentials | REST | Medium | High | Client secret in vault | Simpler server-to-server, fewer security requirements |
| X.509 Certificate | SOAP only | High | Highest | No (certificate-based) | Password-free SOAP, security-sensitive environments |

## Step-by-Step Integration Guide

### 1. Create the Integration System User (ISU)

Every Workday integration starts with an ISU. This is the service account identity your integration uses regardless of the auth method chosen. [src3]

```text
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" and confirm the ISU appears with "Do Not Allow UI Sessions" = Yes.

### 2. Create Security Group and Assign Domain Permissions

The ISU has no permissions until assigned to a security group with explicit domain policies. [src3]

```text
Workday Task: "Create Security Group"

Type: Integration System Security Group (Unconstrained)
Name: ISG_MyApp_Prod
Members: myapp_isu_prod

Then:
Workday Task: "Edit Domain Security Policy Permissions"

Domain:            Worker Data: Public Worker Reports
Security Group:    ISG_MyApp_Prod
Permissions:       Get (read)

Repeat for each domain your integration needs:
  - Worker Data: Workers
  - Staffing: Job Requisition
  - Payroll: Payroll Results
  - etc.

Finally:
Workday Task: "Activate Pending Security Policy Changes"
```

**Verify**: Run "View Security Group Membership for Integration System" and confirm the ISU is listed with correct domains.

### 3. Register an OAuth 2.0 API Client (for REST API)

Required for any REST API access. Links the ISU to an OAuth client. [src5]

```text
Workday Task: "Register API Client for Integrations"

Client Name:           MyApp REST Client
Grant Type:            Jwt Bearer Grant   (for server-to-server)
Access Token Type:     Bearer
X509 Certificate:      <upload public key or "Create x509 Public Key">
Scope:                 Staffing, Human Resources, System
Redirect URI:          (not needed for JWT Bearer)
Non-Expiring Refresh:  (not applicable for JWT Bearer)
Integration System Users: myapp_isu_prod

Record:
  Client ID:       <auto-generated UUID>
  Token Endpoint:  https://wd5-impl-services1.workday.com/ccx/oauth2/<tenant>/token
```

**Verify**: Search "View API Clients" and confirm your client appears with correct grant type and linked ISU.

### 4. Generate X.509 Certificate for JWT Bearer

For JWT Bearer flow, you need an RSA key pair. The public key goes to Workday; the private key stays in your integration. [src4, src6]

```bash
# Generate 2048-bit RSA private key
openssl genrsa -out workday_private.pem 2048

# Generate self-signed X.509 certificate (valid 365 days)
openssl req -new -x509 -key workday_private.pem \
  -out workday_cert.pem -days 365 \
  -subj "/CN=MyApp Workday Integration/O=MyCompany"

# View certificate details to verify
openssl x509 -in workday_cert.pem -text -noout
```

**Verify**: `openssl x509 -in workday_cert.pem -noout -dates` shows valid date range.

### 5. Request an Access Token via JWT Bearer

Build and sign a JWT, then exchange it for an access token at the Workday token endpoint. [src4]

```python
# Input:  Client ID, private key file, ISU username, tenant token endpoint
# Output: OAuth 2.0 access token (Bearer, 60-min lifetime)

import jwt
import time
import 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"
PRIVATE_KEY_PATH = "workday_private.pem"

with open(PRIVATE_KEY_PATH, "r") as f:
    private_key = f.read()

now = int(time.time())
jwt_payload = {
    "iss": CLIENT_ID,
    "sub": ISU_USERNAME,
    "aud": TOKEN_ENDPOINT,
    "iat": now,
    "exp": now + 300,  # 5 minutes max
}

signed_jwt = jwt.encode(jwt_payload, private_key, algorithm="RS256")

resp = requests.post(TOKEN_ENDPOINT, data={
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": signed_jwt,
})

token_data = resp.json()
access_token = token_data["access_token"]
# Use: Authorization: Bearer <access_token>
# Token valid for 60 minutes
```

**Verify**: `resp.status_code == 200` and `access_token` is a non-empty string.

### 6. Make an Authenticated REST API Call

Use the access token to call a Workday REST endpoint. [src2]

```bash
# Input:  Valid access token, tenant REST endpoint
# Output: Worker data in JSON format

curl -s -H "Authorization: Bearer <access_token>" \
  "https://wd5-impl-services1.workday.com/ccx/api/v1/your_tenant/workers?limit=5" \
  | python -m json.tool
```

**Verify**: HTTP 200 response with JSON array of worker records.

### 7. Authenticate via SOAP WS-Security with ISU Password

For SOAP integrations, embed ISU credentials in the WS-Security header. [src3]

```xml
<!-- Input:  ISU username and password, WSDL endpoint -->
<!-- Output: SOAP response with requested data -->

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:bsvc="urn:com.workday/bsvc">
  <soapenv:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsse:UsernameToken>
        <wsse:Username>myapp_isu_prod@your_tenant</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">your_password</wsse:Password>
      </wsse:UsernameToken>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body>
    <bsvc:Get_Workers_Request bsvc:version="v43.0">
      <bsvc:Response_Filter>
        <bsvc:Page>1</bsvc:Page>
        <bsvc:Count>10</bsvc:Count>
      </bsvc:Response_Filter>
    </bsvc:Get_Workers_Request>
  </soapenv:Body>
</soapenv:Envelope>
```

**Verify**: SOAP response contains `<bsvc:Get_Workers_Response>` with worker data.

## Code Examples

### Python: OAuth 2.0 JWT Bearer Token Acquisition with Retry

```python
# Input:  Workday tenant config (client_id, private_key, isu, token_endpoint)
# Output: Valid access token or raises AuthenticationError

import jwt
import time
import requests
from typing import Optional

class WorkdayAuth:
    """Workday OAuth 2.0 JWT Bearer authenticator with token caching."""

    def __init__(self, client_id: str, isu_username: str,
                 private_key_path: str, token_endpoint: str):
        self.client_id = client_id
        self.isu_username = isu_username
        self.token_endpoint = token_endpoint
        with open(private_key_path, "r") as f:
            self.private_key = f.read()
        self._token: Optional[str] = None
        self._token_expiry: float = 0

    def get_token(self) -> str:
        """Return cached token or fetch a new one."""
        if self._token and time.time() < self._token_expiry - 120:
            return self._token  # 2-min buffer before expiry
        return self._refresh_token()

    def _refresh_token(self, retries: int = 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:
                data = resp.json()
                self._token = data["access_token"]
                self._token_expiry = now + 3600  # 60-min lifetime
                return self._token
            if resp.status_code == 429:
                time.sleep(2 ** attempt)  # Exponential backoff
                continue
            resp.raise_for_status()
        raise RuntimeError("Failed to obtain Workday token after retries")
```

### JavaScript/Node.js: OAuth 2.0 Refresh Token Flow

```javascript
// Input:  Workday client_id, client_secret, refresh_token, token_endpoint
// Output: Valid access token string
// Requires: node-fetch@3.x

import fetch from 'node-fetch';

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) {
    const err = await resp.text();
    throw new Error(`Workday auth failed (${resp.status}): ${err}`);
  }

  const data = await resp.json();
  // data.access_token — Bearer token, 60-min lifetime
  // data.refresh_token — new refresh token (store securely, replaces old one)
  return data;
}
```

### cURL: Test OAuth 2.0 Refresh Token Flow

```bash
# Input:  Client ID, Client Secret, Refresh Token, Token Endpoint
# Output: JSON with 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" \
  -d "refresh_token=your_refresh_token" \
  -H "Content-Type: application/x-www-form-urlencoded"

# Expected response:
# {
#   "access_token": "eyJhbG...",
#   "token_type": "Bearer",
#   "expires_in": 3600,
#   "refresh_token": "new_refresh_token_value"
# }
```

## Data Mapping

### Authentication Credential Mapping

| Credential | Where Stored | Where Used | Rotation Policy | Gotcha |
|---|---|---|---|---|
| ISU Username | Workday tenant | SOAP WS-Security header, OAuth `sub` claim | Rarely changes | Must include `@tenant` suffix for SOAP [src3] |
| ISU Password | Integration vault | SOAP WS-Security only | Per org policy; ISU exempt from expiry | No `&`, `"`, `>` characters allowed [src3] |
| Client ID | Workday API Client reg | JWT `iss` claim, Basic Auth header | Does not change | UUID format, auto-generated by Workday [src5] |
| Client Secret | Integration vault | Basic Auth header (non-JWT flows) | Rotate regularly; revoke old | Not used with JWT Bearer flow [src5] |
| X.509 Public Key | Workday tenant | Linked to API client or ISU | Renew before certificate expiry | Must re-upload to Workday on renewal [src6] |
| X.509 Private Key | Integration vault (NEVER Workday) | JWT signing, SOAP WS-Security signing | Matches public key lifecycle | Loss = re-register public key in Workday [src6] |
| Refresh Token | Integration vault | Token endpoint (refresh_token grant) | Rotates on each use | New token returned with each refresh [src2] |
| Access Token | In-memory only | Authorization: Bearer header | 60-min auto-expiry | Do not persist to disk or database [src2] |

### Data Type Gotchas

- **ISU username format differs by API surface** — SOAP requires `username@tenant` format; OAuth uses just the username in the JWT `sub` claim. Mixing these up causes silent auth failures. [src3]
- **Token endpoint URL is tenant-specific** — `https://<host>/ccx/oauth2/<tenant>/token`. Using the wrong tenant returns a generic 401 with no helpful error message. [src2]
- **Refresh tokens are single-use** — each refresh returns a new refresh token. If your integration retries a failed refresh with the old token, Workday may revoke the entire token chain. [src2]

## Error Handling & Failure Points

### Common Error Codes

| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 Unauthorized | Invalid or expired credentials | Wrong ISU password, expired token, missing Bearer prefix | Re-authenticate; check ISU password, regenerate token [src2] |
| 403 Forbidden | Insufficient permissions | ISU security group lacks domain access | Add domain permissions, activate pending security policy changes [src3] |
| 429 Too Many Requests | Rate limit / throttling | Exceeded ~5 req/sec or concurrent session limit | Exponential backoff: wait 2^n seconds (max 32s), retry up to 5 times [src7] |
| 400 Bad Request | Malformed token request | Wrong grant_type, expired JWT assertion, bad client credentials | Verify JWT claims (iss, sub, aud, exp); check client_id format [src4] |
| 500 Internal Server Error | Workday server-side issue | Transient platform error | Retry with backoff; check Workday status page if persistent [src7] |
| SOAP Fault: SignatureVerificationFailed | X.509 signature invalid | Certificate mismatch, wrong private key, or corrupted message | Re-verify certificate pairing; check WS-Security header format [src6] |

### Failure Points in Production

- **Certificate expiry breaks JWT Bearer silently**: X.509 certs expire based on the `-days` parameter at generation. Workday returns a generic 401 with no "certificate expired" detail. Fix: `Set calendar alerts 30 days before cert expiry; automate renewal with cert-manager or similar`. [src6]
- **Refresh token chain breaks on retry**: If a token refresh call fails after Workday has already issued a new refresh token, retrying with the old token fails permanently. Fix: `Always persist the new refresh token atomically before using the access token; implement a dead-letter mechanism for broken chains`. [src2]
- **ISU deactivation cascades to all OAuth tokens**: Deactivating or disabling an ISU immediately invalidates all OAuth tokens linked to it, breaking every integration using that ISU. Fix: `Use separate ISUs per integration; never deactivate without checking linked API clients first`. [src3]
- **Security policy activation is not instant**: After editing domain permissions, you must run "Activate Pending Security Policy Changes." Forgetting this step means the ISU gets 403 despite correct configuration. Fix: `Always include activation as a mandatory step in your runbook; verify with a test API call`. [src3]
- **Bi-annual release changes auth behavior without notice**: Workday R1/R2 updates can modify OAuth scope definitions, deprecate SOAP versions, or change WS-Security header requirements. Fix: `Test all auth flows in sandbox tenant during preview window (4-6 weeks before production release)`. [src7]
- **Multi-tenant confusion**: Using a token endpoint URL for tenant A while targeting data in tenant B returns 401. Fix: `Parameterize tenant name in all endpoint URLs; never hardcode tenant identifiers`. [src2]

## Anti-Patterns

### Wrong: Storing ISU password in code and using it for REST API

```python
# BAD — ISU password cannot authenticate REST API; also exposes credentials
import requests
resp = requests.get(
    "https://wd5.workday.com/ccx/api/v1/tenant/workers",
    auth=("isu_user@tenant", "plain_text_password")  # Will return 401
)
```

### Correct: Use OAuth 2.0 JWT Bearer for REST API

```python
# GOOD — JWT Bearer is token-based, no stored password, 60-min expiry
auth = WorkdayAuth(client_id, isu_user, private_key_path, token_endpoint)
resp = requests.get(
    "https://wd5.workday.com/ccx/api/v1/tenant/workers",
    headers={"Authorization": f"Bearer {auth.get_token()}"}
)
```

### Wrong: Sharing one ISU across multiple integrations

```text
# BAD — No way to audit which integration made which API call
ISU: shared_integration_user
  → Used by: Payroll sync, Benefits connector, Recruiting integration, Custom reports
  → Audit log shows: "shared_integration_user" for everything
```

### Correct: Dedicated ISU per integration system

```text
# GOOD — Clear audit trail, isolated failure domain
ISU: payroll_sync_isu     → Payroll integration only
ISU: benefits_isu         → Benefits connector only
ISU: recruiting_isu       → Recruiting integration only
ISU: reporting_isu        → Custom report extracts only
```

### Wrong: Caching refresh tokens without atomic replacement

```python
# BAD — Race condition: if crash occurs between refresh and persist,
# old refresh token is invalidated but new one is lost
new_tokens = refresh_workday_token(old_refresh_token)
do_api_work(new_tokens["access_token"])  # might crash here
save_refresh_token(new_tokens["refresh_token"])  # never reached
```

### Correct: Persist new refresh token before using access token

```python
# GOOD — Atomic token rotation: persist first, use second
new_tokens = refresh_workday_token(old_refresh_token)
save_refresh_token(new_tokens["refresh_token"])  # persist FIRST
do_api_work(new_tokens["access_token"])  # safe to fail here
```

## Common Pitfalls

- **Forgetting `@tenant` suffix in SOAP ISU username**: SOAP WS-Security requires `username@tenant` format. Omitting the tenant suffix returns an ambiguous "authentication failed" error. Fix: `Always append @tenant_name to the ISU username in SOAP headers`. [src3]
- **Setting non-expiring refresh tokens**: Workday allows this but it violates security best practices and most compliance frameworks. Fix: `Set refresh tokens to expire (e.g., 90 days) and implement automated re-authorization before expiry`. [src5]
- **Not activating pending security policy changes**: Permission changes are staged, not live. The ISU appears correctly configured but gets 403 on API calls. Fix: `Run 'Activate Pending Security Policy Changes' after every domain permission edit; add to deployment checklist`. [src3]
- **Using the same X.509 certificate for dev and prod**: Certificate compromise in dev immediately exposes production. Fix: `Generate separate certificates per environment; upload each to the corresponding Workday tenant`. [src6]
- **Hardcoding token endpoint tenant name**: Multi-environment deployments break when tenant names differ (e.g., `myco_sandbox` vs `myco`). Fix: `Store tenant name as an environment variable; construct the token endpoint URL dynamically`. [src2]
- **Ignoring Workday's bi-annual release impact on auth**: SOAP WSDL versions and OAuth scope definitions can change with R1/R2. Fix: `Subscribe to Workday Community release notes; test auth in sandbox during preview window`. [src7]

## Diagnostic Commands

```bash
# 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 if certificate and private key match (both hashes should be identical)
openssl x509 -in workday_cert.pem -noout -modulus | openssl md5
openssl rsa -in workday_private.pem -noout -modulus | openssl md5

# Test SOAP endpoint with ISU credentials (basic connectivity)
curl -v -X POST "https://wd5-impl-services1.workday.com/ccx/service/your_tenant/Human_Resources/v43.0" \
  -H "Content-Type: text/xml" \
  -d '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body/></soapenv:Envelope>'

# 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 a new self-signed X.509 certificate (2048-bit RSA, 1 year)
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 model refinements | Review scope assignments in API client registration |
| 2025R1 | 2025-03 | Supported | Minor WSDL version updates | No breaking auth changes |
| 2024R2 | 2024-09 | Supported | OAuth 2.0 scope model updates | May require re-registration of API clients with updated scopes |
| 2024R1 | 2024-03 | EOL (approaching) | PKCE support added for public clients | Optional — existing flows unaffected |

### Deprecation Policy

Workday supports SOAP API versions for approximately 2-3 years. WSDL versions are tied to Workday releases. REST API v1 is stable and not versioned in the same way — breaking changes are rare and announced in Workday Community release notes. Always test auth flows in sandbox during the 4-6 week preview window before each R1/R2 production rollout. [src7]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Automated server-to-server integration with Workday HCM/Financials/Payroll | One-time manual data extract | Workday Report Writer or manual CSV export |
| Building a custom integration that reads/writes worker or financial data | Using a pre-built iPaaS connector (Workato, MuleSoft, Boomi) | iPaaS connector handles auth internally |
| Need password-free authentication for compliance (SOC 2, ISO 27001) | Testing or prototyping only (short-lived) | ISU + Password for quick SOAP testing |
| Multi-tenant SaaS connecting to customer Workday tenants | Single-use script that runs once and exits | Simple ISU + Password with short-lived scope |
| Need fine-grained audit trail per integration | Integration handled entirely within Workday Studio | Studio manages its own ISU context |

## Important Caveats

- Workday does not publish comprehensive rate limits — the ~5 req/sec figure is observed behavior, not a contractual guarantee. Actual limits vary by tenant size and Workday's infrastructure decisions.
- OAuth scopes in Workday map to functional areas (Staffing, Payroll, etc.), not individual API endpoints — granting a scope gives access to all endpoints within that functional area.
- Sandbox and production tenants are separate environments with separate ISUs, API clients, and certificates — there is no cross-environment token portability.
- Workday's bi-annual release cycle means authentication behavior can change twice per year — always validate in sandbox during the preview window.
- This card covers authentication patterns only — for Workday REST API endpoint reference, SOAP WSDL details, or specific data model documentation, refer to the official Workday Community documentation (requires login).

## Related Units

- [Oracle ERP Cloud Authentication](/business/erp-integration/oracle-erp-cloud-authentication/2026) — comparable cloud ERP OAuth + certificate authentication patterns
- [NetSuite TBA vs OAuth 2.0](/business/erp-integration/netsuite-tba-vs-oauth2/2026) — alternative ERP authentication comparison
