# Workday

This page covers everything a developer needs to deploy the Incode x Workday Identity Verification (IDV) middleware integration (v1 Lite).

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**

1. Workday Hire business process fires an HTTP callout → `POST /trigger`.
2. Middleware authenticates with Incode, creates an IDV session, and returns the verification URL.
3. Workday delivers the URL to the employee.
4. The employee completes ID scan in Incode.
5. Incode fires a webhook → `POST /webhook`.
6. 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_credentials` flow
- Workday OAuth2 `refresh_token` grant (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 Dashboard                                        |
| Integration Reference ID | From Dashboard: identifies the workflow               |
| Auth URL                 | `https://auth.demo.incode.com/oauth2/token` (demo)    |
| API URL                  | `https://demo-api.incodesmile.com` (demo)             |

> ⚠️ Warning
>
> All session creation calls require the header `api-version: 1.0`. Without it, the API returns `406 Not Acceptable`.

### Workday

**Create an Integration System User (ISU)**

1. Search Workday for **Create Integration System User**.
2. Enter a **Name**: for example, `Incode_IDV_ISU`.
3. Check **Do Not Allow UI Sessions**.
4. Save.

**Create a Security Group and Assign Domain Permissions**

1. Search Workday for **Create Security Group** and select the type Integration System Security Group.
2. Enter a **Name**: for example, `Incode IDV Integration`.
3. Add `Incode_IDV_ISU` as a member.
4. Search Workday for **Maintain Permissions for Security Group** and select `Incode IDV Integration`.
5. Under **Domain Security Policy Permissions**, add:

| Domain                       | Access      |
| ---------------------------- | ----------- |
| `National ID Identification` | Get and Put |

6. Under **Business Process Security Policy Permissions**, add:

| Business Process        | Permission Type   |
| ----------------------- | ----------------- |
| `Change Government IDs` | Initiating Action |

7. Save.
8. Search Workday for **Activate Pending Security Policy Changes**.
9. Submit.

**Register an API Client for Integrations**

1. Search Workday for **Register API Client for Integrations**.
2. Enter a **Client Name**: for example, `Incode IDV Client`.
3. Set **Non-Expiring Refresh Tokens** to **Yes**.
4. Save.
5. Copy the **Client ID** and **Client Secret**.

**Generate a Refresh Token**

1. Search Workday for **Manage Refresh Tokens for Integrations**.
2. Select the ISU, then click **Generate New Refresh Token**.
3. Copy the token value.

**Configure the Hire Business Process HTTP Callout**

In the Hire business process, add an Integration step that POSTs to `/trigger` with this body:

```json
{
  "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

```bash
# 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 validation
```

***

## API Endpoints

### `POST /trigger`

Receives the Workday callout. Creates an Incode IDV session and returns the verification URL.

**Request Body**

```json
{
  "workerID": "12345",
  "fullName": "Jane Doe",
  "personalEmail": "jane@example.com",
  "tenantURL": "https://impl.wd12.myworkday.com/ccx/service/mytenant",
  "workdayClientId": "...",
  "workdayClientSecret": "...",
  "workdayRefreshToken": "...",
  "integrationReference": "...",
  "linkValidityMinutes": 1440
}
```

**Response**

```json
{ "url": "https://incode.me/verify/..." }
```

### `POST /webhook`

Receives Incode `SESSION_SUCCEEDED` events. If `WEBHOOK_SECRET` is set, validates the HMAC-SHA256 signature (header: `x-incode-signature`). Looks up the session context by `externalCustomerId`, fetches a fresh Workday OAuth token, then writes `Change_Government_IDs` via SOAP.

### `GET /health`

Returns `{ "status": "ok" }`. Use for load balancer health checks.

***

## Deployment

### **Local Testing with Ngrok**

```bash
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 endpoint
```

### Production

```bash
npm install --production
NODE_ENV=production node src/index.js
```

Deploy 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 are as follows:

| 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_SECRET` set 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 and Secret saved
- [ ] Refresh token generated and saved
- [ ] Hire BP HTTP callout configured with correct field mapping

**Middleware**

- [ ] All env vars populated
- [ ] `/health` returns `{ status: 'ok' }`
- [ ] `/trigger` tested manually—returns IDV URL
- [ ] `/webhook` tested with sample payload—writes to Workday
- [ ] TLS enabled on public endpoint
- [ ] Session store replaced with Redis for production

<br />
