Skip to main content

How the Workday integration runs

Purpose of this document​

This is the operational reference for the Deeper Signals ↔ Workday integration. It complements the Workday Integration Configuration Guide, which covers tenant-side setup. Where the configuration guide answers "what do I need to set up in Workday?", this document answers "what does Deeper Signals actually do at runtime, and when?"

Each lifecycle below is laid out the same way: a sequence diagram of the Workday calls involved, a metadata block (trigger, cadence, idempotency, observability), and a per-step explanation of what is being asked of Workday and why. The document is intended for tenant administrators and IT teams who need to understand integration traffic, monitor it, or troubleshoot it.

:::info Reference

For OAuth scopes, Domain Security Policies, and the Integration System User configuration referenced throughout, see Workday Integration Configuration Guide — Part 1. :::

High-level architecture​

Deeper Signals runs four independent execution paths against your Workday tenant. Three are recurring polls driven by our scheduler; one is event-driven and fires when a candidate or worker finishes an assessment in our platform.

Figure 1 — Deeper Signals services and the Workday API surfaces they call. Solid arrows show traffic; dashed arrows show OAuth Bearer-token attachment.

Recurring jobs at a glance​

The diagram below plots a one-hour window of recurring traffic. The four jobs run independently — they do not coordinate, and they may overlap. Each job acquires its own distributed lock so a slow run never causes the next tick to pile up.

Figure 2 — One hour of recurring traffic. Tick frequencies: assess-candidate poll 10 min, hired-candidate poll 15 min, worker sync 30 min; the OAuth token is fetched on demand before a job and cached about 1 h (drawn here at 20 min.

JobCadenceWorkday APIs touchedSection
OAuth access tokenOn demand, cached ~1 h/ccx/oauth2/{tenant}/token1.1
Assess-candidate pollEvery 10 minutesSOAP Recruiting: Get_Assess_Candidate, Get_Candidates, Get_Job_Requisitions, Get_Organizations2.1
Assessment completion write-backOn assignedAssessmentFinishedSOAP Recruiting: Assess_Candidate, Put_Candidate_Attachment; REST customObject/v22.2
Hired-candidate pollEvery 15 minutesSOAP Recruiting: Get_Candidates2.3
Worker syncEvery 30 minutesSOAP Human_Resources: Get_Workers3.1
Worker write-backOn assignedAssessmentFinished (worker)SOAP Staffing: Put_Worker_Document; REST customObject/v23.2
Org hierarchy buildTied to HRIS sync (30 min)(internal — no Workday call)3.3

How to read the lifecycle pages​

Each lifecycle in Parts 1--3 follows the same structure:

  • Sequence diagram — every Workday call, in order, with the participant who initiated it.
  • Lifecycle metadata — trigger, cadence, idempotency, and observability hook in a four-cell block.
  • Step-by-step rationale — for each numbered step in the diagram, what we ask Workday for, what Workday returns, and why we need it.

The conventions used in the sequence diagrams:

NotationMeaning
From_Moment / To_MomentWorkday SOAP timestamp request fields used to bound an incremental query.
Updated_From / Updated_ToVariant of the bounded-time pattern, used by Get_Candidates.
Transaction_Date_RangeVariant of the bounded-time pattern, used by Get_Workers.
?bulk=true&updateIfExists=trueIdempotent REST upsert: insert if missing, overwrite if present, in a single batch.
Dashed arrowResponse from Workday back to Deeper Signals.
opt / altConditional branch executed only when the condition holds.

Authentication lifecycle​

1.1 OAuth 2.0 access token (on demand)​

All Workday calls — SOAP and REST alike — are authenticated with a short-lived Bearer access token. Deeper Signals exchanges the long-lived refresh token (configured once on the API Client for Integrations) for a fresh access token on demand, caches it per integration and renews it shortly before it expires. It caches the result in-process for all four execution paths to share.

Trigger First Workday call of a jobCadence On demand; cached until ~1 minute before expiryIdempotency Safe — a new token can be issued at any timeObservability Authentication errors in the integration log

Why we refresh ahead of expiry​

Workday access tokens expire after one hour. Renewing a cached token a minute before expiry guarantees that no in-flight job holds an expired token, even if a single tick is delayed by lock contention or a slow Workday response. The refresh is a single POST with the refresh-token grant type; it does not consume any business-data quota.

POST /ccx/oauth2/{tenant}/token HTTP/1.1
Host: {tenant}.workday.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&client_id={client-id}&client_secret={client-secret}&refresh_token={non-expiring-refresh-token}

:::warning If the token request fails

The job that needed the token logs the authentication error and stops; the next scheduled tick tries again. The most common cause is a rotated client secret — see Configuration Guide §1.6. :::

ATS lifecycles (Recruiting)​

2.1 Assess-candidate poll (every 10 minutes)​

This is the entry point of the ATS integration. It detects candidates who have just been moved into a Deeper Signals assessment step in Workday Recruiting and bootstraps everything we need on our side: candidate, recruiter, requisition, and supervisory-organisation manager.

TriggerSymfony Scheduler tickCadenceEvery 10 minutesIdempotencyIdempotent: parked events without a Reference ID are retried each tickObservabilityAssess-poll lag

Figure 3 — Per-tick flow when one or more candidates are detected in a Deeper Signals assessment step. The ATS→HRIS lane (single-worker sync) is internal to Deeper Signals — no additional Workday call is made on that arrow.

Step-by-step rationale​

Step

Workday call

Why we make it

2 -- 3

Get_Assess_Candidate with From_Moment / To_Moment equal to last poll → now

Returns every candidate that entered an Assess Candidate step since our last successful tick. This is the primary detection hook.

4

(no call — local park)

If Workday has not yet stamped an Assessment_Test_Reference on the event (sometimes lags a few seconds in Workday), we park it in an internal retry queue and re-evaluate on the next tick. Without the reference we cannot match the event to a configured Recruiting Assessment Test.

5 -- 6

Get_Candidates by candidate ID

Returns the candidate's email address, first name, last name and Workday identifiers (Candidate_ID, WID, and the referenced job requisition). Email is required to invite the candidate and is the key we match them on; the names personalise the invitation and the assessment intro. No other personal data is read from the candidate record — see the note below this table.

7 -- 8

Get_Job_Requisitions by requisition WID

Returns the recruiter reference, the supervisory organisation reference, and the job title. The title is folded into the campaign name — this happens on every deployment. Resolving the recruiter reference to a person, and making them the campaign owner, requires an HRIS integration; in an ATS-only deployment the reference is read but never resolved.

9 -- 10

Get_Organizations by Sup. Org WID

HRIS deployments only. Returns the supervisory organisation's manager, who is added to the same single-worker sync as the recruiter so that team analytics see them later. This call is gated on an HRIS integration being configured and is not made in an ATS-only deployment.

11

(no call — internal HRIS sync)

HRIS deployments only. The recruiter and Sup. Org manager are upserted into our user directory, making the campaign assignable to a real DS account. In an ATS-only deployment no worker record is read or created, and the campaign is created without an auto-assigned owner.

12

(no call — campaign clone)

We resolve or clone the campaign template named Workday - <Title> - <ReqID>. Cloning preserves the recruiter as owner and reuses any custom intro text from the configured template.

13

(no call — outbound email)

The candidate receives the assessment invitation. From this point on, control is theirs until completion.

:::warning Candidate personal data — the complete list

The only fields Deeper Signals reads from a Workday candidate record are: email address, first name, last name, and the Workday identifiers used for correlation (Candidate_ID, Candidate WID, Applicant_ID, Worker_ID once hired, and the referenced job requisition ID/WID).

Not requested, not read, not stored: phone number, mobile number, postal address, country, nationality, date of birth, gender, ethnicity, disability status, veteran status, salary or compensation data, résumé/CV content, application questionnaire responses, and social media identifiers. Earlier revisions of this document listed phone and country among the fields read by Get_Candidates; that was a generic ATS capability description and was never accurate for the Workday integration — no such fields exist on the internal candidate model. :::

:::info Why we resolve the manager — and when we don't

Hiring managers occasionally complete the same assessment as part of calibration. By syncing the Sup. Org manager up front, we avoid a "phantom user" later if they are tagged as the assessment reviewer.

Steps 9--11 run only when an HRIS integration is configured. An ATS-only deployment skips them entirely: Get_Organizations is never called, no worker data is read, and the corresponding Workday security domains are not required. See §1.3 of the Integration Guide for the reduced ATS-only permission set. :::

2.2 Assessment completion write-back (event-driven)​

When a candidate finishes the Deeper Signals assessment in our platform, we publish an internal event that fans out to every integration tagged on the candidate's invitation. The Workday handler runs once per Workday-tagged invitation and writes three artefacts back to the Job Application: the human-readable status & score, the rendered PDF report, and the per-dimension custom-object scores.

TriggerInternal event assignedAssessmentFinishedCadenceOnce per completed assessmentIdempotencyREST upsert is idempotent; SOAP write is retried with replay keyObservabilityPer-message handler log + DLQ on failure

Figure 4 — From candidate completion to three artefacts on the Job Application.

Step-by-step rationale​

StepWorkday callWhy we make it
1 -- 3(no Workday call yet)Internal: completion event is dispatched on the message bus and routed to the Workday handler if the candidate has any invitations tagged with this integration.
4 -- 5Assess_Candidate with status, score, results URLUpdates the Job Application's assessment status to Deepersignals_Completed and stamps the recruiter-visible score. This is what flips the candidate visually in Workday's recruiter UI.
6(no call — local PDF render)We render the candidate's report PDFs (Core Drivers and, where licensed, Drivers Pro) so they can be attached in the next step. The render is fully local; no Workday data is needed.
7 -- 8Put_Candidate_Attachment with category OTHER_DOCUMENTSAttaches the rendered PDF report(s) to the Job Application. OTHER_DOCUMENTS is a Workday-owned category — it does not need to be created in your tenant.
9 -- 10POST /api/customObject/v2/candidateDeeperSignal…?bulk=true&updateIfExists=trueWrites the per-dimension scores into the custom object so they can be reported on, filtered, and surfaced in Workday Reporting alongside other recruitment data. The bulk + updateIfExists flags make the call idempotent — replays will not duplicate rows.

:::warning Order matters

The status update (step 4) is performed before the PDF and custom-object writes. If the recruiter UI flips to Completed but the artefacts have not appeared yet, expect a one- to two-second gap while the bulk REST call settles. :::

2.3 Hired-candidate poll (every 15 minutes)​

Once a candidate is hired in Workday, they get a Worker_ID and stop being "just a candidate" — they become a worker. This poll detects that transition for every candidate we've already invited and merges their candidate identity with the worker identity coming from the HRIS sync.

TriggerSymfony Scheduler tickCadenceEvery 15 minutesIdempotencyIdempotent: re-running re-evaluates the same window without duplicating writesObservabilityHired-poll lag

Figure 5 — Detecting candidate → worker transition and re-firing assessment write-back to the worker side.

Step-by-step rationale​

StepWorkday callWhy we make it
3 -- 4Get_Candidates with Updated_From / Updated_ToReturns every candidate whose record has changed since the last hired-poll tick. We filter the response client-side for those that now carry a Worker_ID.
5(no call — local lookup)Find every prior assessment invitation that points at this candidate. These are the records of every assessment they've already received from us.
6(no call — duplicate resolution)If the HRIS sync has already created a placeholder worker for this person (typical when there's a small clock skew between recruiting and HCM), we merge the placeholder into the candidate's user account — a candidate-to-worker reconciliation step.
7(no call — local mark)Mark every prior invitation "HIRED" and store the Worker_ID on the user account. From this point on, the user is treated as a worker, not a candidate.
8 -- 9Re-dispatch the completion eventIf the HRIS integration is also active, we re-fire the completion write-back for every assessment they previously finished — but this time the handler runs against the worker side (Part 3.2), not the candidate side. This is what makes pre-hire assessments visible on the new hire's worker profile.

:::info Two integrations, one event source

Steps 8--9 illustrate the design principle behind Deeper Signals' Workday integrations: the same internal completion event drives both candidate and worker write-back. The candidate version fires immediately on completion (Part 2.2); the worker version fires retroactively at hire time. :::

HRIS lifecycles (Workers)​

3.1 Worker sync (every 30 minutes)​

The worker sync is the spine of the HRIS integration. It pulls every worker that has changed since the last successful tick, applies your field mapping, upserts the user in our directory, captures the manager relationship, and feeds team and hierarchy services.

TriggerSymfony Scheduler tickCadenceEvery 30 minutes (incremental)IdempotencyUpsert by email; rerunning a window has no side effectsObservabilityHRIS sync lag

Figure 6 — Per-tick flow of the incremental worker sync. Pagination is at 50 workers per page; an integration lock prevents two ticks from overlapping.

Step-by-step rationale​

StepWorkday callWhy we make it
2 -- 3(no call — lock acquisition)The integration acquires its distributed lock so a slow tick cannot overlap with the next one. The lock is per-tenant.
4(no call — window calculation)The query window is the previous sync timestamp → now. On the first run we use a configured backfill horizon; on subsequent runs we are fully incremental.
5 -- 6Get_Workers with Transaction_Date_RangeReturns one page of workers (max 50). Workday paginates server-side; we follow until exhausted. Using the transaction-date range means we only ever transfer workers whose data actually changed.
7 -- 11(no calls — per-worker processing)For each worker on the page: apply field mapping, upsert the Deeper Signals user keyed on email, persist the worker's HRIS dataset row, capture the Workday Manager WID, and — if a termination date is now set — mark the user inactive.
12 -- 14(no Workday call — internal team service)For every synced worker we update an internal team index keyed off the captured manager relationship — the worker is added as a member of their manager's team. Empty teams are removed in a sweep at the end.
15 -- 16(no Workday call — internal visibility-index rebuild)The org-hierarchy build (covered in §3.3) runs after the page loop completes. This is what makes manager-of-manager visibility work in our admin UIs.
17 -- 18(no Workday call — search-index update)The HRIS dataset is indexed for search so that team analytics, filters, and saved views remain fast on tenants with hundreds of thousands of workers.

:::info Why incremental, not full

A full pull on a 50,000-worker tenant takes minutes and is rate-limited by Workday's per-tenant integration quota. Incremental sync uses two timestamps and is typically <30 seconds even on large tenants. :::

3.2 Worker write-back (event-driven)​

Counterpart to §2.2 on the worker side. When a worker (or a recently-hired candidate, see §2.3) finishes a Deeper Signals assessment, we attach the rendered PDF to their worker record and write the per-dimension scores to the worker's custom object.

TriggerInternal event assignedAssessmentFinishedCadenceOnce per completed assessment (worker side)IdempotencyREST upsert is idempotent; SOAP write is retried with replay keyObservabilityPer-message handler log + DLQ on failure

Figure 7 — Worker write-back. The custom object is only visible on the worker profile if it is added to a Profile Group.

Step-by-step rationale​

StepWorkday callWhy we make it
1 -- 2(no Workday call yet)Internal: completion event is dispatched and routed to the worker write-back handler. Conditions: the user must have a worker_id and the HRIS integration must be active.
3(no call — local PDF render)Render the worker-facing PDF report. Same engine as the candidate report, different recipient.
4 -- 5Put_Worker_Document with category ASSESSMENTAttaches the report to the worker record. ASSESSMENT is a tenant-owned Document_Category_ID — it must exist in your tenant. See Configuration Guide §3.3.
6 -- 7POST /api/customObject/v2/workerDeeperSignal…?bulk=true&updateIfExists=trueWrites per-dimension scores. The custom object only renders on the worker profile if it has been added to a Profile Group — that's a tenant configuration step, not a per-call action.

3.3 Org hierarchy build (after every sync)​

At the end of every successful HRIS sync, Deeper Signals rebuilds an internal manager-of-manager visibility index from the per-worker Manager_Reference captured during sync. This is what makes admin queries like "show me everyone Anna can see, including her direct reports' reports" return in milliseconds. The build is a full rebuild on every tick, runs entirely on the Deeper Signals side, and triggers no additional Workday calls.

TriggerEnd of every HRIS syncCadenceTied to sync (30 min)IdempotencyFull rebuild, no incremental stateObservabilityBuild duration logged per account

Full API call inventory​

Every Workday operation Deeper Signals invokes, grouped by service and surface.

SOAP — Recruiting v45.2​

OperationUsed byPurpose
Get_Assess_Candidate2.1Detect candidates entering an Assess Candidate step.
Get_Candidates2.1, 2.3Resolve candidate email, first and last name, and Workday identifiers (2.1); detect hires by Worker_ID transition (2.3).
Get_Job_Requisitions2.1Resolve recruiter, supervisory organisation, and title.
Get_Organizations2.1Resolve the supervisory organisation's manager.
Assess_Candidate2.2Write status, score, and results URL back to the Job Application.
Put_Candidate_Attachment2.2Attach the rendered PDF report to the Job Application.

SOAP — Staffing v45.2​

OperationUsed byPurpose
Put_Worker_Document3.2Attach the rendered PDF report to the worker record.

SOAP — Human_Resources v45.2​

OperationUsed byPurpose
Get_Workers3.1Incremental worker pull driven by Transaction_Date_Range.

REST — customObject v2​

OperationUsed byPurpose
POST /api/customObject/v2/candidateDeeperSignal…?bulk=true&updateIfExists=true2.2Idempotent upsert of per-dimension scores on the Job Application custom object.
POST /api/customObject/v2/workerDeeperSignal…?bulk=true&updateIfExists=true3.2Idempotent upsert of per-dimension scores on the worker custom object.

OAuth — Token endpoint​

OperationUsed byPurpose
POST /ccx/oauth2/{tenant}/token1.1Exchange the long-lived refresh token for a one-hour access token, every 20 minutes.

Triggers, cadence, and idempotency​

One-page cheat sheet across all six lifecycles.

LifecycleTriggerCadenceIdempotent?Failure mode
OAuth access token (§1.1)Any jobOn demand, cached ~1 hYesThe calling job logs the error and stops; the next tick retries.
Assess-candidate poll (§2.1)SchedulerEvery 10 minYes — parked events retried each tickTick logged with error; next tick re-scans the same window.
Completion write-back (§2.2)EventPer assessmentREST yes; SOAP via replay keyGoes to DLQ; retried by ops or replayed manually.
Hired-candidate poll (§2.3)SchedulerEvery 15 minYesSame window re-scanned next tick.
Worker sync (§3.1)SchedulerEvery 30 min (incremental)Yes — upsert by emailLock released, next tick covers the missed window.
Worker write-back (§3.2)EventPer assessment (worker side)REST yes; SOAP via replay keyGoes to DLQ; retried or replayed.
Hierarchy build (§3.3)After sync30 min (tied to sync)Yes — full rebuildOld closure table preserved if rebuild fails mid-flight.

:::note Closing note

This document is a living reference and tracks the integration as it currently runs in production. If you spot a divergence between what's described here and what you observe in your tenant, raise it with your Deeper Signals contact and we'll reconcile it in the next revision. :::