Workday HCM connector
The Workday HCM connector imports workers and supervisory organizations from your Workday tenant. IdentityMesh treats Workday as the source-of-truth for employee master data and projects worker records into the mesh, where they can be joined with downstream identity systems (AD, Entra ID, Okta) by the same join rules that already drive your AD ↔ Entra integration.
Phase 1 was REST-only and read-only. Phase 2, new in this release, adds a SOAP transport for tenants that expose Workday Web Services but not an OAuth2 API client, optional import of positions, compensation, benefits and photos, custom fields, and a deliberately narrow write-back of preferred name and work email. Pilot the phase-2 paths on an implementation tenant first.
Overview
| Aspect | Value |
|---|---|
| Direction | Inbound (Workday → IdentityMesh), plus a narrow outbound write-back of preferred name and work email |
| Object types | User (Workday Workers) + OrgUnit (Supervisory Organizations); Position when ImportPositions is on |
| Transports | SOAP (Workday Web Services) or REST (/ccx/api/v1) for the list feeds; SOAP is always the enrichment and write-back path |
| Auth mode | SOAP: ISU username and password sent as a WS-Security UsernameToken. REST: OAuth2 client credentials |
| Watermark | lastUpdatedDateTime per object type |
| Hard-delete signal | employmentStatus == "Terminated" → IsDeleted=true |
| Still deferred | HR provisioning (hires, job changes, terminations) into Workday; Workday Audit report integration for hard-delete detection |
If you operate Workday as your HRIS and Active Directory or Entra
ID as your identity provider, run this connector alongside the AD
or Entra connector — they project into the same mesh objects, and
the merge is governed by the join rules configured in the Admin UI
(typically join on employeeNumber or email).
Prerequisites
- A Workday tenant where you can create an Integration System User (ISU). For the REST transport you also need to register an OAuth2 API client; for the SOAP transport the ISU’s username and password are enough. Workday admin or HR Admin role is typically required for this setup.
- The tenant’s API host (e.g.
wd5-impl-services1.workday.com) and tenant name (e.g.contoso_preview) — both visible from the Workday admin home page or the URL operators use to log in. - The connector DLL in the
Connectors/folder of whichever host runs the import. It ships as a default connector in both the Sync Engine MSI and the Relay Agent MSI, with no separate feature to select, so a relay-routed Workday connector needs no manual DLL copy.
Workday tenant setup
Choose a transport first. Tenants that expose Workday Web Services (WWS) but have not enabled OAuth2 API clients use SOAP; tenants with an API client use REST. Both need an ISU.
SOAP (ISU username and password)
- Create the ISU with Create Integration System User and give it a password that does not expire. Exempt it from password rotation and MFA rules the way you would any other integration account.
- Grant the ISU the security groups listed under REST step 2
below. Write-back additionally needs the domain security policies
that allow
Change_Preferred_NameandChange_Work_Contact_Information. - Note the WWS endpoint version your tenant publishes; the
endpoint pattern is
https://{host}/ccx/service/{tenant}/Human_Resources/{version}. Set it asSoapApiVersion(below).
The ISU password is provisioned into the IdentityMesh secret store like any other secret and referenced from the connector’s authentication settings; it is sent to Workday as a WS-Security UsernameToken over HTTPS on every call.
REST (OAuth2 client credentials)
-
Register the integration application in Workday:
- Open Edit Tenant Setup - Security and enable OAuth 2.0 Clients for Integrations.
- Run the Register API Client for Integrations task. Provide
a friendly name (e.g.
IdentityMesh-prod) and select a Workday account that becomes the integration system user (ISU). - On completion, Workday returns the Client ID and the one-time Client Secret. Save both immediately — the secret cannot be retrieved later.
-
Grant the ISU read access to the worker and organization data. Recommended security groups:
Worker Data: Public Worker ReportsWorker Data: Active and Terminated WorkersOrganization Data: View Organization
Avoid blanket
System Administratorgrants. Document the exact security groups in your change-control system; Workday audits every change to ISU permissions. -
Test the OAuth2 token endpoint from a workstation:
curl -X POST \ "https://{tenant}.workday.com/ccx/oauth2/{tenant_name}/token" \ -d "grant_type=client_credentials" \ -u "<client_id>:<client_secret>"A successful response contains an
access_tokenvalid for ~30 minutes.
Configure IdentityMesh
-
Provision the secret via
secretscli. For REST that is the OAuth2 client secret; for SOAP it is the ISU password:secretscli set secret://workday/{tenant_name}/clientsecret "<the client secret>" secretscli set secret://workday/{tenant_name}/isu-password "<the ISU password>" -
Create the connector via the Admin UI or REST. The portal editor offers both authentication modes; pick one and it shows only that mode’s fields. Test Connection and Discover attributes run on the connector’s assigned engine instance when it is online, otherwise on another online engine instance, or on the selected relay agent; the button says which, and stays visible with the reason when none is online. Sample
configJsonfor a SOAP tenant:{ "TenantHost": "wd5-impl-services1.workday.com", "TenantName": "contoso_preview", "ApiMode": "Soap", "SoapApiVersion": "v46.1", "PageSize": 100, "ImportWorkers": true, "ImportOrganizations": true, "ImportPositions": false, "ImportCompensation": false, "ImportBenefits": false, "ImportPhotos": false, "ExternalIdAttribute": "id" }And for a REST tenant:
{ "TenantHost": "wd5-impl-services1.workday.com", "TenantName": "contoso_preview", "Realm": "contoso_preview", "ApiMode": "Rest", "PageSize": 100, "ImportWorkers": true, "ImportOrganizations": true, "ImportPositions": false, "ExternalIdAttribute": "id" }Realmtypically equalsTenantName; some Workday tenants distinguish between the two — check with your Workday admin. -
Wire the secret reference on the connector’s authentication settings. REST uses the client id plus a
secret://workday/{tenant_name}/clientsecretreference; SOAP uses the ISU username plus asecret://workday/{tenant_name}/isu-passwordreference (Mode = UsernamePassword). IdentityMesh resolves the reference at run time. A REST tenant may carry both credential sets; the OAuth2 bearer is used for the list feeds and the ISU UsernameToken for SOAP enrichment and write-back. -
Add a join rule in the Admin UI mapping
Workday.employeeNumber→mesh.employeeNumber(or whatever anchor your existing AD / Entra connectors use). This is what joins a Workday worker into an existing mesh object created by the IdP connector.
Settings reference
| Setting | Default | What it does |
|---|---|---|
ApiMode | Auto (empty) | Soap, Rest or Auto. Auto picks SOAP when only a username and password are present, and REST when only a client id and secret are present. Set it explicitly when both credential sets exist |
ImportPositions | false | Import open and filled positions as mesh object type Position (REST /positions, or SOAP Get_Positions on the Staffing service) |
ImportCompensation | false | Enrich workers with compensation data from SOAP Get_Workers. Sensitive; opt in deliberately |
ImportBenefits | false | Enrich workers with benefits data from SOAP Get_Workers |
ImportPhotos | false | Enrich workers with profile photos from SOAP Get_Workers |
EnrichmentBatchSize | 25 | Worker IDs per SOAP enrichment call when the list feed is REST (clamped to 1–100). On the SOAP path enrichment is folded into the list page |
PhotoMaxBytes | 262144 | Photos larger than this are not stored; hasPhoto and photoOmitted are still set |
SoapApiVersion | v42.0 | WWS version segment of the endpoint. SOAP-only tenants should set the version their endpoint publishes |
SoapOrganizationService | Human_Resources | WWS service used for Get_Organizations. Override with Staffing if your tenant serves organizations there |
SoapPositionService | Staffing | WWS service used for Get_Positions. Leave ImportPositions off if the tenant has no Staffing service |
WorkerCustomFields | [] | alias = spec lines copied onto each worker. xml:<path> reads the SOAP response (ID[@type=…] selectors, @attribute, several matches joined with ;); a plain spec is a REST property name or dot-path (on SOAP it only re-labels an attribute already imported). Discover attributes writes them |
PositionCustomFields | [] | The same alias = spec lines for positions |
PreferredNameCountryCode | USA | ISO 3166-1 alpha-3 code sent with a preferred-name write-back; Workday requires a country reference on that call |
PreferSoapWorkers | false | Compatibility alias from the first SOAP preview: treated as ApiMode = Soap when ApiMode is empty. Prefer ApiMode |
FullImportBudgetMinutes | 45 | How long one run of a full sweep reads before it stops at a page boundary, saves its place and lets the next scheduled run continue. 0 reads the whole tenant in one run. See “Full imports run in chunks” |
Adding attributes
The worker, organization and position fields in the tables below are a
fixed list. Anything beyond them comes from WorkerCustomFields and
PositionCustomFields (one line each, in the connector form or the raw
configuration JSON). A line is alias = spec; the alias is optional. An
alias may not reuse a built-in attribute name or another line’s alias, and
Discover refuses to write lines that would.
-
Discover attributes. Beside Test Connection, Discover attributes asks the probe target (the connector’s assigned engine instance when online, otherwise another online engine instance, or the selected relay agent) to sample ten workers (and positions or organizations when those imports are on) and lists every attribute path the tenant actually returned, with one sample value. Tick the rows you want, edit the alias if you like, and Add selected writes the lines for you. Rows already covered by the fixed mapping are marked imported as … and cannot be ticked. Sample values in sections whose names mention identification, national or tax IDs, passports, visas, bank, pay or compensation, benefits, photos, birth, gender, ethnicity or race, disability, religion, marital status, sexual orientation, military or veteran status, citizenship, death, tobacco use, medical or health are masked. A field that is empty on every sampled worker does not appear. For a Direct connector (one that runs on the Sync Engine rather than a relay agent), Discover runs on an online engine instance; the relay path applies only to connectors with a relay selected, and that relay needs the current Workday plugin. An older relay plugin, or an older service that predates attribute discovery, answers with a plain connection test and the panel says so.
-
SOAP tenants take
xml:paths into theGet_Workersresponse, relative to theWorkerelement. Segments are element names separated by/; anIDsegment may select by type; a final@Namereads an attribute. Several matches are joined with;.nationalId = xml:Worker_Data/Personal_Data/Identification_Data/National_ID/National_ID_Data/ID countryAlpha2 = xml:Worker_Data/Personal_Data/Name_Data/Preferred_Name_Data/Name_Detail_Data/Country_Reference/ID[@type=ISO_3166-1_Alpha-2_Code] formattedName = xml:Worker_Data/Personal_Data/Name_Data/Preferred_Name_Data/Name_Detail_Data/@Formatted_Name -
REST tenants take JSON property names or dot-paths (
compensation.basePay); anxml:line yields nothing there. On SOAP tenants a plain spec names a property of the mapped item (an attribute name from the tables below), which only re-labels an attribute the connector already imports; usexml:for anything else.
Save, then run a full import; the new attribute then appears in the attribute dropdowns on join and flow rules. Workday Integration Field Overrides are not requested by the connector today.
What gets synced
Worker → User
| Workday field | IdentityMesh attribute | Notes |
|---|---|---|
id | ExternalId | Workday’s stable WID; default ExternalIdAttribute |
workerType | workerType | “Employee” / “Contingent Worker” |
employmentStatus | employmentStatus | “Active”, “Terminated”, “On Leave” |
| derived from above | accountEnabled | true when status is Active |
| derived from above | IsDeleted | true when status is Terminated |
firstName / lastName / preferredName | same | |
email / workEmail | same | |
workPhone / mobilePhone | same | |
employeeNumber / companyEmployeeId | same | typical join key |
jobTitle / jobProfile / businessTitle | same | |
costCenter / costCenterCode | same | |
companyCode / legalEntity | same | |
supervisor / manager | manager | the manager’s worker id; mesh-side joins resolve to a User |
location / locationCode / country / region | same | |
hireDate / terminationDate | same | ISO 8601 date |
lastUpdatedDateTime / lastUpdatedBy | same | drives delta sync |
Compensation, benefits and photo fields are excluded unless the
matching Import* flag is on. Sensitive payroll data lives on a
different audit / permission boundary, so the opt-in is explicit and
per category; review the ISU’s security groups before enabling
ImportCompensation.
How reference fields resolve
Workday returns most values as references: a bag of IDs of different
types and, on some tenants, a Descriptor. Display attributes
(country, jobProfile, location, workerType, timeType,
payFrequency, payCurrency, compensationGrade, compensationPackage) resolve as the
descriptor when present, otherwise the readable ID for that reference
(ISO_3166-1_Alpha-2_Code for country, Job_Profile_ID, Location_ID,
…), otherwise any ID that is not a WID, and only then the WID. Join keys
(manager, supervisoryOrganization,
primaryWorkerSupervisoryOrganization, parentOrganizationId) are
always the WID, never the descriptor, so they match the ExternalId of
the imported worker or organization. If you need the WID of a display
reference, add an xml: line ending in ID[@type=WID].
Position (optional)
With ImportPositions enabled, each open or filled position is
imported as a Position object with its own external id, so
customers that model positions as first-class identities can join
workers to positions with ordinary join rules. Add position fields
you need beyond the defaults through PositionCustomFields.
Supervisory Organization → OrgUnit
| Workday field | IdentityMesh attribute | Notes |
|---|---|---|
id | ExternalId | |
name | name | |
code | code | |
parentOrganizationId | parentOrganizationId | builds the org hierarchy |
manager | manager | worker id of the org owner |
lastUpdatedDateTime | lastUpdatedDateTime |
Organization membership is not emitted as a separate
GroupMember object type. Each worker carries
primaryWorkerSupervisoryOrganization as an attribute, which is
sufficient for downstream join rules to project users into the
right group / OU. Flat membership rows remain a follow-up request.
Watermark and delta behaviour
Watermarks are ISO-8601 lastUpdatedDateTime timestamps stored
per object type, encoded as a single JSON string the engine
treats opaquely:
{"w": "2026-04-26T08:30:00.000Z", "o": "2026-04-26T08:25:00.000Z", "p": "2026-04-26T08:20:00.000Z"}
(w = workers, o = organizations, p = positions; the p key is
present only when ImportPositions is on). Both transports use the
same watermark shape.
The connector tolerates legacy bare-string watermarks for in-flight upgrades — if it sees a plain ISO timestamp instead of the wrapper, both feeds use that timestamp as their starting point.
Hard-delete gap
Workday does not typically purge worker records — terminated
workers retain a Terminated employment status and remain visible
in the API indefinitely. The connector maps
employmentStatus == "Terminated" to a delete change with
IsDeleted=true, which is the correct soft-delete signal for
downstream provisioning.
If your Workday tenant is configured to purge worker records (rare,
but possible for contingent workers), schedule a periodic full
sync (set the connector’s Mode = Full on a weekly cadence) to
catch hard-deletes. A later release will close this gap via the
Workday Audit report API.
Full imports run in chunks
A full import of a large tenant takes longer than the engine’s
import-run timeout (Timeout (minutes) on the Schedule page,
default 60), and a run that times out keeps nothing: the
watermark is not advanced, so the next attempt starts again from
worker zero. Without chunking, a tenant too large to read within the
timeout would fail every full import the same way.
Since this release a full sweep is resumable across runs. The
connector reads pages until FullImportBudgetMinutes (default 45)
is spent, stops at a page boundary, and returns what it has together
with a cursor (the object type it was on and its offset). The engine
commits that chunk, stores the cursor inside the connector’s
watermark under a resume key, records the run with status
Continuing, and leaves the schedule due, so the next one-minute
scheduler tick starts the next chunk. Chunks run workers, then
organizations, then positions; a resumed chunk skips the types it
already finished.
What you will see on the Runs page:
- One row per chunk, status Continuing, with Scanned climbing as pages arrive. The last chunk reports Completed.
- The connector log on a resumed chunk reads
DeltaWatermark=none…, Resume=type w, offset 37500, chunk 3. The blank delta watermark is expected: a full import ignores it, and the cursor rides beside it. - Deleted on a Continuing chunk is not delete-by-absence. The connector requests inactive workers too and surfaces terminated ones as soft-deletes, so a slice heavy in terminated workers reports them as Deleted.
Three rules follow from the design:
- Delete-by-absence waits for the sweep to finish. Objects not seen since the sweep started are marked deleted only when the last chunk completes, using the sweep’s start time, so a worker read in chunk 1 is not “absent” in chunk 3. A sweep that never finishes never deletes; after 100 chunks the connector logs a warning that the tenant may be growing faster than the budget allows. Raise the budget or the schedule frequency.
- Run now with the Full override restarts the sweep. It clears the watermark, cursor included. Let a running sweep finish its chunks instead.
- Delta imports never chunk. Only
Mode = Fullschedules, or a run that finds a cursor, sweep.
A chunk that fails keeps the incoming cursor, so a retry resumes rather than restarts. The REST feed has no stable sort, so a hire between chunks can shift offsets: a worker read twice is a harmless upsert, and a worker skipped that way is caught by the next delta. Pilot chunked sweeps on a Direct connector before routing the connector through a relay agent.
Throttling
Workday’s REST API enforces per-tenant rate limits (configurable
per Workday environment). The connector handles 429 Too Many Requests responses with Retry-After-aware retry: 3 retries with
exponential back-off capped at 30 seconds. Beyond that it surfaces
the error to the run history.
If you see frequent 429s in the connector logs, work with Workday Support to raise the integration tenant’s rate limit — IdentityMesh makes one request per page (100 workers per page by default), so even a 100k-worker tenant fits inside the standard 1000 req/hour bucket.
Export (write-back)
Export is new in this release and is not HR provisioning. The
connector writes exactly two things back to Workday, and only on
User updates:
| Mesh change | Workday operation |
|---|---|
| Preferred name | Change_Preferred_Name (with PreferredNameCountryCode) |
| Work email | Change_Work_Contact_Information |
Anything else in an export batch — an add, a delete, or an update carrying any other attribute — fails with a confirmation error that names the unsupported operation. It does not throw, so one unsupported change fails its own queue item rather than the batch, and the engine never records a change Workday did not receive.
Write-back always goes over SOAP. SOAP tenants authenticate it with the ISU UsernameToken; REST tenants reuse the OAuth2 bearer and may also send the UsernameToken when ISU credentials are configured. Grant the ISU only the two domain security policies the operations above need.
Security model
- App-only auth — no user impersonation; the OAuth2 token or the UsernameToken represents the ISU, not a logged-in user.
- Least-privilege scope — the recommended security groups grant read access; add the two write-back policies only if you enable export. Workday’s audit log records every API call the ISU makes; review it after the first sync to confirm the call pattern matches expectations.
- Secret rotation — rotate the OAuth2 client secret or the ISU
password every 12 months minimum (more often if your security
policy mandates). The rotation procedure is the same as any other
secret managed through
secretscli; seesecret-rotation.md.
Still deferred
The following remain out of scope and are documented as follow-ups:
- HR provisioning — hires, job changes and terminations written into Workday from the mesh. Export is limited to preferred name and work email (see above).
- Workday Audit report integration — for true hard-delete detection without periodic full syncs.
- Flat organization membership rows — organization membership
is still carried on the worker, not emitted as
GroupMember.
Related runbooks
secret-rotation.md— secret lifecyclesecrets-keyvault.md— vault-backed secret storage when DPAPI isn’t enough (Azure)secrets-vault.md— vault-backed storage for HashiCorp customersconnector-entra.md— pair Workday with Entra ID to project HR data into your cloud directoryconnector-okta.md— pair Workday with Okta to drive provisioning from the source-of-truthrelay-agent.md— for Workday tenants behind a customer firewall (rare; Workday is SaaS)