Paycom HR Source
nexusID integrates with Paycom as an authoritative HR source and drives the full joiner/mover/leaver (JML) lifecycle from it — no SailPoint or third-party connector required. nexusID is the IdP + governance platform, so Paycom data flows straight into provisioning, deprovisioning, access reviews, and the tamper-evident audit chain.
How it flows
Paycom (SFTP CSV or REST API)
→ PaycomProvider.fetchRecords() (integration/hr-paycom)
→ PaycomConnector.upsert() (match by employeeId)
→ LifecycleEventService onJoiner / onMover / onLeaver
→ LifecycleScheduleExecutor (PROVISION → ENABLE → DISABLE)
→ Entra / AD / SCIM / downstream provisioning + deprovisioning
→ HR source precedence + WorkflowEngine (JOINER / MOVER / LEAVER)
Lifecycle decisions live in the application layer; the provider only owns the external Paycom concern, so the two delivery modes below are interchangeable from the pipeline's point of view.
Configure in the admin console (recommended)
Add Paycom as an HR_SOURCE integration in Admin → Integrations — no environment variables,
no redeploy. Pick the Paycom provider and fill in the config form (rendered from the provider's
schema): the delivery mode (SFTP or API) and the matching fields — SFTP host / username /
password (or privateKeyPath) / remotePath, or API apiBaseUrl / apiEmployeesPath / apiSid
apiToken. Secrets are encrypted at rest, and the unified HR sync sweep picks it up. See HR Sources for the model.

The PAYCOM_SYNC_* variables below are an optional alternative for GitOps / air-gapped deployments
that keep secrets out of the database. They're off by default — most operators should use the
admin console above.
Mode 1 — SFTP flat-file (env, legacy)
Paycom exports an employee CSV to SFTP; nexusID pulls and parses it on a schedule.
PAYCOM_SYNC_ENABLED=true
PAYCOM_SYNC_MODE=SFTP # default
PAYCOM_SYNC_HOST=sftp.paycomonline.com
PAYCOM_SYNC_USERNAME=...
PAYCOM_SYNC_PASSWORD=... # or PAYCOM_SYNC_PRIVATE_KEY_PATH=/path/key
PAYCOM_SYNC_REMOTE_PATH=/outbound/employees.csv
PAYCOM_SYNC_CRON="0 0 */6 * * *" # every 6h
PAYCOM_SYNC_STRICT_HOST_KEY_CHECKING=true # production
Expected CSV columns (snake_case or CamelCase, missing columns tolerated):
employee_id (required, match key), email, first_name, last_name, display_name,
department, job_title, manager, status.
Mode 2 — Paycom REST API (env, legacy)
Pull from Paycom's API instead of a file. Endpoint, auth, response shape, and field names are all configurable, so the client adapts to your Paycom contract.
PAYCOM_SYNC_ENABLED=true
PAYCOM_SYNC_MODE=API
PAYCOM_SYNC_API_BASE_URL=https://api.paycomonline.net
PAYCOM_SYNC_API_EMPLOYEES_PATH=/api/v1/employee
PAYCOM_SYNC_API_SID=... # + PAYCOM_SYNC_API_TOKEN=... → Basic auth
PAYCOM_SYNC_API_RECORDS_PATH=data # omit if the response body IS the array
PAYCOM_SYNC_API_PAGE_PARAM=page # omit for a single request
Authentication options:
- SID + token → nexusID builds a
Basic base64(sid:token)header, or - Raw header → set
apiAuthHeader+apiAuthValue(e.g.Authorization: Bearer <token>).
Field mapping
Each canonical field maps to a dot-path within one employee JSON object. Defaults match snake_case
names; override apiFieldMap for Paycom-native names (e.g. eecode, nested name.first):
| Canonical | Default path | Example override |
|---|---|---|
employeeId (required) | employee_id | eecode |
email | email | work.email |
firstName / lastName | first_name / last_name | name.first / name.last |
department / jobTitle | department / job_title | deptDesc / work.title |
manager / status | manager / status | supervisor / employmentStatus |
status is upper-cased; values like TERMINATED/TERMED/INACTIVE trigger the leaver path.
Scheduling & near-real-time
PaycomSyncScheduler polls on PAYCOM_SYNC_CRON (default every 6h). Lower the interval (e.g.
0 */15 * * * *) for near-real-time change detection. Changes are detected by diffing material
attributes per employeeId.
Multi-source precedence
When Paycom is one of several HR feeds, HR Source Precedence decides which
source wins per attribute (default order includes PAYCOM). Provenance is tracked per field.
Outbound (nexusID → Paycom write-back)
nexusID can push nexusID-originated lifecycle changes back to Paycom: termination on leaver and (optionally) attribute updates on mover. It's off by default and never writes to your payroll system of record unless explicitly enabled and configured. Writes are best-effort — a Paycom write failure never breaks the JML flow — and Paycom-originated events are skipped so they aren't echoed back.
Per Paycom's API, auth is APISID/APIToken headers (or Basic / OAuth Bearer), termination is
DELETE /employees/{id} (or a status update), and updates are PUT/PATCH /employees/{id}. All of
this is configurable, since Paycom's write API varies by tenant.
PAYCOM_SYNC_OUTBOUND_ENABLED=true
PAYCOM_SYNC_OUTBOUND_BASE_URL=https://api.paycom.com/v1
PAYCOM_SYNC_OUTBOUND_AUTH_STYLE=HEADERS # HEADERS (APISID/APIToken) | BASIC | RAW
PAYCOM_SYNC_OUTBOUND_SID=... PAYCOM_SYNC_OUTBOUND_TOKEN=...
PAYCOM_SYNC_OUTBOUND_EMPLOYEE_PATH_TEMPLATE=/employees/{id}
PAYCOM_SYNC_OUTBOUND_TERMINATE_MODE=DELETE # DELETE | STATUS
PAYCOM_SYNC_OUTBOUND_UPDATE_METHOD=PUT # PUT | PATCH
PAYCOM_SYNC_OUTBOUND_UPDATE_ON_MOVER=false # set true to push attribute changes too
For STATUS-mode termination, set PAYCOM_SYNC_OUTBOUND_TERMINATE_STATUS_FIELD /
...TERMINATE_STATUS_VALUE (default status = Terminated).
Implemented by PaycomWriteClient (integration/hr-paycom) + PaycomOutboundService
(api/scim), invoked from the leaver/mover hooks in LifecycleEventService.
Where it lives in code
- Provider:
modules/integration/hr-paycom/.../PaycomProvider.kt(+PaycomApiClient.kt) - Connector + scheduler + config:
modules/api/scim/.../connector/paycom/ - Lifecycle:
LifecycleEventService,LifecycleScheduleExecutor(see Reconciliation)