Workday
This guide covers everything a developer needs to deploy the Incode x Workday Identity Verification (IDV) middleware integration (v1 Lite).
Overview
When a new hire is created in Workday, this integration automatically sends them an Incode identity verification link. When they complete the scan, the result is written back to Workday as a Government ID record.
Integration flow:
- Workday Hire business process fires an HTTP callout →
POST /trigger - Middleware authenticates with Incode, creates an IDV session, returns the verification URL
- Workday delivers the URL to the employee
- Employee completes ID scan in Incode
- Incode fires a webhook →
POST /webhook - Middleware exchanges refresh token for a Workday OAuth token, then writes the IDV result via SOAP
Change_Government_IDs
Tech stack:
- Node.js / Express (3 endpoints:
POST /trigger,POST /webhook,GET /health) - Incode OAuth2
client_credentialsflow - Workday OAuth2
refresh_tokengrant (ISU machine-to-machine) - Workday SOAP API v43.0 — Human_Resources web service
Prerequisites
Incode
| Item | Notes |
|---|---|
| B2B onboarding enabled | Required on your Incode tenant |
| OAuth2 API Client | client_credentials grant — save Client ID and Secret |
| API Key | From Incode Dashboard |
| Integration Reference ID | From Incode Dashboard — identifies the workflow |
| Auth URL | https://auth.demo.incode.com/oauth2/token (demo) |
| API URL | https://demo-api.incodesmile.com (demo) |
All session creation calls require the header
api-version: 1.0. Without it, the API returns406 Not Acceptable.
Workday
Step 1 — Create an Integration System User (ISU)
- Workday search → Create Integration System User
- Name: e.g.
Incode_IDV_ISU - Check Do Not Allow UI Sessions
- Save
Step 2 — Create a Security Group and assign domain permissions
- Workday search → Create Security Group → type: Integration System Security Group
- Name: e.g.
Incode IDV Integration - Add
Incode_IDV_ISUas a member - Workday search → Maintain Permissions for Security Group → select
Incode IDV Integration - Under Domain Security Policy Permissions, add:
| Domain | Access |
|---|---|
National ID Identification | Get and Put |
- Under Business Process Security Policy Permissions, add:
| Business Process | Permission Type |
|---|---|
Change Government IDs | Initiating Action |
- Save → Workday search → Activate Pending Security Policy Changes → submit
Step 3 — Register an API Client for Integrations
- Workday search → Register API Client for Integrations
- Client Name: e.g.
Incode IDV Client - Non-Expiring Refresh Tokens: Yes
- Save → copy Client ID and Client Secret
Step 4 — Generate a Refresh Token
- Workday search → Manage Refresh Tokens for Integrations
- Select the ISU → Generate New Refresh Token
- Copy the token value
Step 5 — Configure the Hire Business Process HTTP Callout
In the Hire business process, add an Integration step that POSTs to /trigger with this body:
{
"workerID": "{{Employee_ID}}",
"fullName": "{{Legal_Name}}",
"personalEmail": "{{Personal_Email}}",
"tenantURL": "https://impl.wd12.myworkday.com/ccx/service/your_tenant",
"workdayClientId": "{{Client_ID}}",
"workdayClientSecret": "{{Client_Secret}}",
"workdayRefreshToken": "{{Refresh_Token}}",
"integrationReference": "{{Integration_Reference_ID}}",
"linkValidityMinutes": 1440
}Environment variables
# Incode
INCODE_CLIENT_ID=
INCODE_CLIENT_SECRET=
INCODE_AUTH_URL=https://auth.demo.incode.com/oauth2/token
INCODE_API_URL=https://demo-api.incodesmile.com
INCODE_API_KEY=
INCODE_INTEGRATION_REFERENCE=
LINK_VALIDITY_MINUTES=1440
# Workday
WORKDAY_TENANT=your_tenant_name
WORKDAY_CLIENT_ID=
WORKDAY_CLIENT_SECRET=
WORKDAY_REFRESH_TOKEN=
WORKDAY_ISU_USERNAME=Incode_IDV_ISU
AUTO_COMPLETE=true
# Middleware
PORT=3000
WEBHOOK_SECRET= # optional — for HMAC signature validationAPI endpoints
POST /trigger
POST /triggerReceives the Workday callout. Creates an Incode IDV session and returns the verification URL.
Request body:
{
"workerID": "12345",
"fullName": "Jane Doe",
"personalEmail": "[email protected]",
"tenantURL": "https://impl.wd12.myworkday.com/ccx/service/mytenant",
"workdayClientId": "...",
"workdayClientSecret": "...",
"workdayRefreshToken": "...",
"integrationReference": "...",
"linkValidityMinutes": 1440
}Response:
{ "url": "https://incode.me/verify/..." }POST /webhook
POST /webhookReceives Incode SESSION_SUCCEEDED events. Validates HMAC-SHA256 signature (header: x-incode-signature) if WEBHOOK_SECRET is set. Looks up the session context by externalCustomerId, fetches a fresh Workday OAuth token, then writes Change_Government_IDs via SOAP.
GET /health
GET /healthReturns { "status": "ok" }. Use for load balancer health checks.
Deployment
Local testing with ngrok:
npm install
cp .env.example .env # fill in all values
node src/index.js
# In a second terminal:
ngrok http 3000
# Use the ngrok HTTPS URL as your Incode webhook endpointProduction:
npm install --production
NODE_ENV=production node src/index.jsRecommend deploying behind a reverse proxy (nginx / AWS ALB) with TLS termination. The service is stateless except for the in-memory session store — for production, replace sessionStore with Redis.
National ID type code mapping
Workday National_ID_Type_Code values are country-specific, formatted as {ISO3166Alpha3}-{Suffix}. The middleware auto-derives codes from the issuing country and document type returned by Incode OCR. Common mappings:
| Incode document type | Issuing country | Workday code |
|---|---|---|
passport | IN (India) | IND-PAS |
passport | JP (Japan) | JPN-PAS |
passport | BY (Belarus) | BLR-PAS |
national_id | US | USA-SSN |
national_id | MX | MEX-CURP |
passport | US | USA-SSN |
For country-specific exceptions, add entries to COUNTRY_DOC_OVERRIDES in src/utils/documentTypeMap.js.
Workday SOAP — critical notes
| Topic | Detail |
|---|---|
| OAuth token placement | Bearer token in HTTP Authorization header — not in WS-Security SOAP header |
| Services host | Sandbox: impl-services1.wd12.myworkday.com (different from UI host impl.wd12.myworkday.com) |
| SOAP endpoint | https://{services-host}/ccx/service/{tenant}/Human_Resources/v43.0 |
| Person reference | Use Person_Reference (not Worker_Reference) inside Change_Government_IDs_Data |
| Verification date | xsd:date format — YYYY-MM-DD only, no time component |
| Country reference type | ISO_3166-1_Alpha-2_Code |
| ID type reference | National_ID_Type_Code |
| Replace_All | Set to false to preserve existing IDs |
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
Incode invalid_client | Wrong client secret | Re-paste INCODE_CLIENT_SECRET directly — do not retype |
Incode 406 Not Acceptable | Missing api-version header | Add api-version: 1.0 to session creation request |
Incode Employee by login factor cannot be found | Email not in Incode | Use an email that exists in the Incode tenant |
| Workday 404 on token endpoint | Wrong host | Use services host, not UI host |
Workday invalid_client (OAuth) | Misread client ID/secret | Paste directly from Workday — don't retype |
Workday invalid username or password | Tenant has disabled SOAP Basic Auth | Switch to OAuth refresh_token grant |
SOAP The task submitted is not authorized | Missing BP security policy | Add ISU group to Change Government IDs BP Initiating Actions |
SOAP Invalid Subelement Worker_Reference | Wrong SOAP schema | Use Person_Reference inside Change_Government_IDs_Data |
SOAP PASSPORT is not a valid National_ID_Type_Code | Generic codes don't exist | Use country-specific code e.g. IND-PAS |
SOAP Invalid ID type Country_ID | Wrong type attribute | Use ISO_3166-1_Alpha-2_Code |
Go-live checklist
Incode
- OAuth client created, credentials saved
- API key configured
- Integration Reference ID confirmed
- Webhook URL registered pointing to
/webhook -
WEBHOOK_SECRETset and matches Incode dashboard
Workday
- ISU created with "Do Not Allow UI Sessions"
- Security group created, ISU assigned
- Domain permission:
National ID Identification— Get and Put - BP policy:
Change Government IDs— Initiating Action - Security policy changes activated
- API Client registered, Client ID + Secret saved
- Refresh token generated and saved
- Hire BP HTTP callout configured with correct field mapping
Middleware
- All env vars populated
-
/healthreturns{ status: 'ok' } -
/triggertested manually — returns IDV URL -
/webhooktested with sample payload — writes to Workday - TLS enabled on public endpoint
- Session store replaced with Redis for production
Updated about 1 hour ago
