Switching digital assessment vendors is one of the higher-risk integration changes a talent acquisition team can make. The hiring funnel runs through your assessment platform at every stage: ATS-triggered invitations, candidate completions, score sync back into candidate records, shortlist generation, and interview scheduling. Break any link in that chain and you'll see it in your metrics within days, potentially as a measurable drop in completion rates, delayed shortlists, or broken reporting.
This documentation covers the full migration lifecycle: prerequisites and dependency mapping, data export schemas and field mapping, API re-authentication patterns with sample requests, end-to-end staging test cases, and the compliance requirements that affect how you structure the migration itself. Use it as a QA playbook alongside your vendor onboarding documentation.
Before writing a single line of migration code, establish exactly what data you're moving. Assessment platform migrations typically involve:
candidate_id, external candidate_uuid, language preference, consent statusINVITED, IN_PROGRESS, COMPLETED, EXPIRED, WITHDRAWN)Anything not explicitly listed here needs a written decision: migrate, discard, or archive.
This is where most migrations create silent data corruption. Your ATS, assessment platform, and identity provider each assign identifiers to the same candidate, and they don't match.
Define a canonical identifier hierarchy before you write any mapping logic:
candidate_id: the stable internal key in your system of record; use this as the primary join keycandidate_uuid: a provider-issued UUID that the assessment platform assigns; must be stored in ATS as a custom attribute during migrationThe source of truth for identity resolution is your ATS. Every imported record should carry the original ATS candidate_id. If your current provider didn't store that field, you'll need a reconciliation query against email + invite timestamp before you can reliably join historical records.
Complete all of the following before any data export or API configuration work begins:
ATS → Assessment Platform
├─ Invitations (ATS triggers invite via API or webhook)
├─ Completions (Assessment platform POSTs completion event to ATS webhook)
├─ Report retrieval (ATS GETs report via REST or embeds URL)
├─ Score sync (Assessment platform PATCHes candidate attributes in ATS)
└─ Interview scheduling (Scheduling system reads shortlist from ATS)
Breaking any node in this graph silently degrades a downstream step without necessarily throwing an error.
A clean migration preserves pipeline performance. Platforms like Selection Lab report measurable impact KPIs: 27% fewer candidate drop-offs and 15 minutes saved per applicant (Selection Lab Main Deck 2026). Those are the numbers you're protecting during the switch. Set explicit monitoring targets for days 1 through 14 post-cutover:
| Metric | Baseline (prior 14 days) | Stop-the-line threshold |
|---|---|---|
| Assessment completion rate | Measure and record | Drop > 5 pp |
| Invite-to-start rate | Measure and record | Drop > 8 pp |
| Report sync success rate | ~100% | Drop below 98% |
| Webhook delivery failure rate | < 1% | Exceeds 3% |
| Average time-to-next-stage | Measure and record | Increase > 24 hrs |
Request exports in JSON or structured CSV with deterministic headers. Avoid platform-native export formats (.xlsm, proprietary XML) because field names and value formats vary between provider versions. JSON is preferred for nested structures (e.g., multi-dimension score objects). CSV works well for flat candidate and session records where you need fast validation in spreadsheet tools.
When requesting exports, specify:
2025-11-14T09:30:00+00:00)null vs. omitted field, agree on one convention)"COMPLETED" not "Completed")The schemas below are illustrative templates. Adapt field names to match your providers' actual API contracts.
Candidate
{
"ats_candidate_id": "ATS-78432",
"external_candidate_uuid": "c3f1a2b4-84d2-4e9a-b7c1-0f3e2d1a9b56",
"email": "[email protected]",
"preferred_language": "en",
"consent_given_at": "2025-10-01T14:22:00+00:00",
"consent_version": "v2.1",
"data_retention_expires_at": "2027-10-01T00:00:00+00:00"
}
AssessmentSession
{
"session_id": "sess_9f2e1c3d",
"ats_candidate_id": "ATS-78432",
"external_candidate_uuid": "c3f1a2b4-84d2-4e9a-b7c1-0f3e2d1a9b56",
"assessment_id": "asmt_cognitive_v3",
"assessment_version": "3.2.1",
"rule_version": "rule_2025q4",
"scoring_model_version": "sm_2025.2",
"status": "COMPLETED",
"invited_at": "2025-10-02T08:00:00+00:00",
"started_at": "2025-10-02T09:15:00+00:00",
"completed_at": "2025-10-02T09-48:00+00:00",
"locale": "en-US"
}
AssessmentResult
{
"session_id": "sess_9f2e1c3d",
"ats_candidate_id": "ATS-78432",
"assessment_version": "3.2.1",
"scoring_model_version": "sm_2025.2",
"dimensions": {
"verbal_reasoning": { "raw_score": 28, "percentile": 72 },
"numerical_reasoning": { "raw_score": 31, "percentile": 81 },
"conscientiousness": { "raw_score": 44, "percentile": 65 }
},
"composite_score": 74.2,
"composite_percentile": 76
}
RoleProfileMatch
{
"session_id": "sess_9f2e1c3d",
"role_profile_id": "rp_warehouse_supervisor_v2",
"role_profile_version": "v2.0",
"match_score": 0.81,
"recommendation": "ADVANCE",
"generated_at": "2025-10-02T10:00:00+00:00"
}
AuditEvent
{
"event_id": "evt_a1b2c3d4",
"event_type": "REPORT_ACCESSED",
"actor_type": "RECRUITER",
"actor_id": "usr_recruiter_042",
"session_id": "sess_9f2e1c3d",
"ats_candidate_id": "ATS-78432",
"occurred_at": "2025-10-03T11:05:00+00:00"
}
When backfilling historical results, always tag every imported record with assessment_version, rule_version, and scoring_model_version. Without these tags, your ATS reporting dashboard will mix scores computed under different norm groups or scoring weights, producing averages that mean nothing. A candidate who scored in the 72nd percentile on sm_2024.1 cannot be meaningfully compared to one scored under sm_2025.2 unless your reporting filters by version.
For role profile match backfills, note that match scores are calculated against a specific role_profile_version. If the outgoing provider has updated role profiles over time, you'll need a version-aware backfill query that joins each session to the role profile version active at that session's completed_at timestamp.
GDPR Article 20 covers personal data that the individual provided to a controller, or that was generated through the individual's activity. Platform-generated opinions and predictions (e.g., a proprietary AI-derived "hire recommendation" score) may fall outside portability scope if they represent the controller's own assessment rather than activity data. Confirm with your DPO which fields your provider treats as portable vs. derived. This distinction affects what you can legally import into a new platform and what you'll need to regenerate.
Three patterns cover most assessment platform + ATS integrations:
completion event to an ATS-registered endpoint; fastest for near-real-time score syncGET /assessment-sessions/{session_id}/results; simpler to implement but introduces latencyMost production setups combine pattern 1 (completions via webhook) with pattern 2 (report retrieval on demand) and pattern 3 (nightly reconciliation for missed events).
| Method | Typical use case | Migration consideration |
|---|---|---|
| OAuth 2.0 client credentials | Server-to-server score sync, report retrieval | Rotate client_id + client_secret before cutover; document required scopes explicitly |
| OAuth 2.0 authorization code | Recruiter-authenticated report views | Test token refresh flows in staging before cutover |
| API key | Webhook registration, simple REST pulls | Rotate key in both ATS and assessment platform config simultaneously; do not use old key post-cutover |
| Signed webhook secret (HMAC-SHA256) | Webhook payload verification | Regenerate secret; update ATS webhook handler before activating new endpoint |
Credential rotation checklist:
assessments:read, reports:read, candidates:write)The following examples use placeholder base URLs and field names. Replace with your provider's actual API contract.
POST /api/v1/invitations (create invite with idempotency key)
POST /api/v1/invitations HTTP/1.1
Host: assessment.provider.example
Authorization: Bearer {access_token}
Content-Type: application/json
Idempotency-Key: inv_ATS78432_asmt_cognitive_v3_20251002
{
"ats_candidate_id": "ATS-78432",
"email": "[email protected]",
"assessment_id": "asmt_cognitive_v3",
"locale": "en-US",
"expires_at": "2025-10-09T23:59:00+00:00",
"callback_url": "https://your-ats.example/webhooks/assessment"
}
GET /api/v1/assessment-sessions/{session_id}/results
GET /api/v1/assessment-sessions/sess_9f2e1c3d/results HTTP/1.1
Host: assessment.provider.example
Authorization: Bearer {access_token}
Accept: application/json
POST /webhooks/assessment-complete (inbound payload to your ATS endpoint)
{
"event_id": "evt_a1b2c3d4",
"event_type": "ASSESSMENT_COMPLETED",
"session_id": "sess_9f2e1c3d",
"ats_candidate_id": "ATS-78432",
"completed_at": "2025-10-02T09:48:00+00:00",
"report_url": "https://assessment.provider.example/reports/sess_9f2e1c3d",
"composite_score": 74.2,
"recommendation": "ADVANCE"
}
PATCH /ats/v2/candidates/{ats_candidate_id}/attributes (write score back to ATS)
PATCH /ats/v2/candidates/ATS-78432/attributes HTTP/1.1
Host: your-ats.example
Authorization: Bearer {ats_access_token}
Content-Type: application/json
{
"assessment_session_id": "sess_9f2e1c3d",
"assessment_version": "3.2.1",
"scoring_model_version": "sm_2025.2",
"composite_score": 74.2,
"composite_percentile": 76,
"recommendation": "ADVANCE",
"score_synced_at": "2025-10-02T10:01:00+00:00"
}
Idempotency-Key on all POST /invitations and POST /sessions requests. The key should encode enough context to be unique per candidate+assessment combination (e.g., inv_{ats_candidate_id}_{assessment_id}_{date}).event_id + session_id. Store processed event_id values in a dedup table with a TTL of at least 72 hours.completed_at field will corrupt funnel analytics, particularly time-to-next-stage calculations.Before running any test cases, confirm you have:
Never test cutover procedures against production data. Specifically, don't backfill historical scores into the production ATS until the staging run has passed all verification gates.
Run these seven test types in order. Each must pass before proceeding to the next.
ats_candidate_id resolves correctly; confirm score fields populate in ATS without overwriting existing data200 OK on both delivery attemptsTC-01: Standard completion flow Trigger: Invite a test candidate via ATS. Expected: Candidate receives invite, completes assessment, ATS webhook endpoint receives ASSESSMENT_COMPLETED event within 60 seconds of completion, ATS candidate record updates with composite_score, recommendation, and report_url. Pass criteria: All three fields populated; no duplicate session created.
TC-02: Idempotent re-invite Trigger: Send the same POST /invitations request twice with identical Idempotency-Key. Expected: Provider returns the same session_id on both requests; only one session exists in the staging platform. Pass criteria: Second request returns 200 or 409 with the original session object; no second session created.
TC-03: Historical backfill with version tagging Trigger: Import 50 historical sessions from the old provider, each tagged with assessment_version and scoring_model_version. Expected: All 50 records resolve to existing ATS candidate records; score fields populate correctly; version tags are visible in ATS reporting filters. Pass criteria: Zero unresolved ats_candidate_id values; no score field is null where source data is non-null.
TC-04: Multilingual assessment run Trigger: Invite a test candidate with locale: nl-NL; complete the assessment. Expected: Report payload returns with locale: nl-NL; report content is in Dutch; ATS receives a report_language field matching the locale. Pass criteria: Report language matches requested locale; no fallback to English without explicit configuration.
TC-05: Consent and retention flag verification Trigger: Create an invitation for a candidate with a specific consent version; complete the assessment; request the report. Expected: Consent acceptance event appears in the audit log with consent_version, consent_given_at, and data_retention_expires_at populated. Pass criteria: All three consent fields present in both the assessment platform audit log and the ATS candidate record.
After the first 48 hours of production cutover, run these verification queries against both the old and new provider logs for the overlapping candidate cohort:
COMPLETED sessions: new platform total must be within 2% of expected based on invite volumecomposite_score distribution: should not shift by more than 0.5 standard deviations vs. the prior 14-day baseline (a larger shift may indicate a scoring model version mismatch, not genuine candidate performance change)report_url accessibility: sample 50 report URLs from the new provider; confirm all are accessible with valid credentialsIf completion rate drops more than the agreed threshold (see KPI guardrails table above), invoke the rollback procedure immediately. Rollback means re-pointing ATS invite triggers to the old provider, not just stopping new invites. Have the rollback owner identified and on call for the first 72 hours post-cutover.
Candidate identifier mismatch. Using email as the primary join key fails when candidates change their email address between the original application and the historical import. Always join on ats_candidate_id. If your current provider never stored ats_candidate_id, build a reconciliation query that joins on email + invite timestamp with a tolerance window, and manually review any records where two or more candidates share an email.
Silent schema drift. Providers update field names and enum values without announcing breaking changes. A field that was status: "complete" becomes status: "COMPLETED" after a minor version bump. Implement JSON Schema validation on all inbound webhook payloads and outbound API responses. Fail loudly on unexpected values rather than silently dropping fields.
Duplicate scoring updates. Without idempotent webhook handling, a retry of a ASSESSMENT_COMPLETED event writes a second score record to the ATS, creating ambiguity in reporting. Store processed event_id values and reject duplicates before any write operation.
OAuth scope errors during cutover. A client credential set up with assessments:write instead of assessments:read will succeed in tests but fail in production when a read-only operation is expected. Document the exact required scopes for each integration pattern and test scope errors explicitly in staging by requesting tokens with intentionally wrong scopes.
Clock skew and timestamp parsing. A provider that returns timestamps in local time without a timezone offset will cause funnel analytics (time-to-next-stage, completion latency) to be wrong by hours. Enforce ISO-8601 with explicit UTC offset across all fields. Add a validation step in your data pipeline that rejects or flags any timestamp string without a timezone component.
Privacy and consent sequencing breaks. When switching providers, your candidate-facing consent flow changes. If the new provider presents a consent prompt at a different point in the assessment journey than the old one did, candidates may encounter an unexpected consent screen and abandon. Map the consent prompt sequence explicitly and test it end-to-end in TC-05. Platforms that apply privacy-by-design, such as those that request consent before showing results and again before sharing them (a pattern Selection Lab uses, per its 2026 documentation), give you a clear model for where consent checkpoints should sit.
Assessment content versioning on import. Importing historical results without assessment_version tags means your ATS reports will show score distributions that silently mix cohorts assessed on different norm groups. Tag every imported record with the three version fields from the schema above.
Broken deep links from ATS. When the ATS stores a direct URL to a candidate's assessment session, those URLs break after switching providers. Audit all ATS candidate records and custom fields that contain assessment platform URLs before go-live. Replace or redirect them as part of the cutover procedure, not after.
Analytics parity failure. Your new provider may emit different event names or calculate drop-off at different funnel stages than your old one. Before finalizing the integration, map old event names to new ones explicitly and confirm that your BI or ATS reporting dashboards produce equivalent KPI calculations. A 27% drop-off reduction KPI (Selection Lab, 2026) is only meaningful if the drop-off calculation is consistent before and after the switch.
When an issue occurs during or after cutover, route it through this chain:
session_id, event_id, request_id)ats_candidate_id, session_id, event_idAssign a named rollback owner before cutover begins. That person has the authority to make the rollback call without additional approval. On cutover day, have the following open and monitored continuously:
When submitting a data export request to your outgoing provider:
COMPLETED sessions vs. all sessions)Migrating digital assessment vendors without disrupting your hiring pipeline is an execution problem, not a strategy problem. The tools and patterns above give you a concrete QA framework for validating that every stage of the funnel, from ATS-triggered invitation through score sync to interview scheduling, works correctly before you commit to cutover. Run the test cases, monitor the KPI guardrails, and keep the rollback owner on call.
.png)
Switching digital assessment vendors is one of the higher-risk integration changes a talent acquisition team can make. The hiring funnel runs through your assessment platform at every stage: ATS-triggered invitations, candidate completions, score sync back into candidate records, shortlist generation, and interview scheduling. Break any link in that chain and you'll see it in your metrics within days, potentially as a measurable drop in completion rates, delayed shortlists, or broken reporting.
This documentation covers the full migration lifecycle: prerequisites and dependency mapping, data export schemas and field mapping, API re-authentication patterns with sample requests, end-to-end staging test cases, and the compliance requirements that affect how you structure the migration itself. Use it as a QA playbook alongside your vendor onboarding documentation.
Before writing a single line of migration code, establish exactly what data you're moving. Assessment platform migrations typically involve:
candidate_id, external candidate_uuid, language preference, consent statusINVITED, IN_PROGRESS, COMPLETED, EXPIRED, WITHDRAWN)Anything not explicitly listed here needs a written decision: migrate, discard, or archive.
This is where most migrations create silent data corruption. Your ATS, assessment platform, and identity provider each assign identifiers to the same candidate, and they don't match.
Define a canonical identifier hierarchy before you write any mapping logic:
candidate_id: the stable internal key in your system of record; use this as the primary join keycandidate_uuid: a provider-issued UUID that the assessment platform assigns; must be stored in ATS as a custom attribute during migrationThe source of truth for identity resolution is your ATS. Every imported record should carry the original ATS candidate_id. If your current provider didn't store that field, you'll need a reconciliation query against email + invite timestamp before you can reliably join historical records.
Complete all of the following before any data export or API configuration work begins:
ATS → Assessment Platform
├─ Invitations (ATS triggers invite via API or webhook)
├─ Completions (Assessment platform POSTs completion event to ATS webhook)
├─ Report retrieval (ATS GETs report via REST or embeds URL)
├─ Score sync (Assessment platform PATCHes candidate attributes in ATS)
└─ Interview scheduling (Scheduling system reads shortlist from ATS)
Breaking any node in this graph silently degrades a downstream step without necessarily throwing an error.
A clean migration preserves pipeline performance. Platforms like Selection Lab report measurable impact KPIs: 27% fewer candidate drop-offs and 15 minutes saved per applicant (Selection Lab Main Deck 2026). Those are the numbers you're protecting during the switch. Set explicit monitoring targets for days 1 through 14 post-cutover:
| Metric | Baseline (prior 14 days) | Stop-the-line threshold |
|---|---|---|
| Assessment completion rate | Measure and record | Drop > 5 pp |
| Invite-to-start rate | Measure and record | Drop > 8 pp |
| Report sync success rate | ~100% | Drop below 98% |
| Webhook delivery failure rate | < 1% | Exceeds 3% |
| Average time-to-next-stage | Measure and record | Increase > 24 hrs |
Request exports in JSON or structured CSV with deterministic headers. Avoid platform-native export formats (.xlsm, proprietary XML) because field names and value formats vary between provider versions. JSON is preferred for nested structures (e.g., multi-dimension score objects). CSV works well for flat candidate and session records where you need fast validation in spreadsheet tools.
When requesting exports, specify:
2025-11-14T09:30:00+00:00)null vs. omitted field, agree on one convention)"COMPLETED" not "Completed")The schemas below are illustrative templates. Adapt field names to match your providers' actual API contracts.
Candidate
{
"ats_candidate_id": "ATS-78432",
"external_candidate_uuid": "c3f1a2b4-84d2-4e9a-b7c1-0f3e2d1a9b56",
"email": "[email protected]",
"preferred_language": "en",
"consent_given_at": "2025-10-01T14:22:00+00:00",
"consent_version": "v2.1",
"data_retention_expires_at": "2027-10-01T00:00:00+00:00"
}
AssessmentSession
{
"session_id": "sess_9f2e1c3d",
"ats_candidate_id": "ATS-78432",
"external_candidate_uuid": "c3f1a2b4-84d2-4e9a-b7c1-0f3e2d1a9b56",
"assessment_id": "asmt_cognitive_v3",
"assessment_version": "3.2.1",
"rule_version": "rule_2025q4",
"scoring_model_version": "sm_2025.2",
"status": "COMPLETED",
"invited_at": "2025-10-02T08:00:00+00:00",
"started_at": "2025-10-02T09:15:00+00:00",
"completed_at": "2025-10-02T09-48:00+00:00",
"locale": "en-US"
}
AssessmentResult
{
"session_id": "sess_9f2e1c3d",
"ats_candidate_id": "ATS-78432",
"assessment_version": "3.2.1",
"scoring_model_version": "sm_2025.2",
"dimensions": {
"verbal_reasoning": { "raw_score": 28, "percentile": 72 },
"numerical_reasoning": { "raw_score": 31, "percentile": 81 },
"conscientiousness": { "raw_score": 44, "percentile": 65 }
},
"composite_score": 74.2,
"composite_percentile": 76
}
RoleProfileMatch
{
"session_id": "sess_9f2e1c3d",
"role_profile_id": "rp_warehouse_supervisor_v2",
"role_profile_version": "v2.0",
"match_score": 0.81,
"recommendation": "ADVANCE",
"generated_at": "2025-10-02T10:00:00+00:00"
}
AuditEvent
{
"event_id": "evt_a1b2c3d4",
"event_type": "REPORT_ACCESSED",
"actor_type": "RECRUITER",
"actor_id": "usr_recruiter_042",
"session_id": "sess_9f2e1c3d",
"ats_candidate_id": "ATS-78432",
"occurred_at": "2025-10-03T11:05:00+00:00"
}
When backfilling historical results, always tag every imported record with assessment_version, rule_version, and scoring_model_version. Without these tags, your ATS reporting dashboard will mix scores computed under different norm groups or scoring weights, producing averages that mean nothing. A candidate who scored in the 72nd percentile on sm_2024.1 cannot be meaningfully compared to one scored under sm_2025.2 unless your reporting filters by version.
For role profile match backfills, note that match scores are calculated against a specific role_profile_version. If the outgoing provider has updated role profiles over time, you'll need a version-aware backfill query that joins each session to the role profile version active at that session's completed_at timestamp.
GDPR Article 20 covers personal data that the individual provided to a controller, or that was generated through the individual's activity. Platform-generated opinions and predictions (e.g., a proprietary AI-derived "hire recommendation" score) may fall outside portability scope if they represent the controller's own assessment rather than activity data. Confirm with your DPO which fields your provider treats as portable vs. derived. This distinction affects what you can legally import into a new platform and what you'll need to regenerate.
Three patterns cover most assessment platform + ATS integrations:
completion event to an ATS-registered endpoint; fastest for near-real-time score syncGET /assessment-sessions/{session_id}/results; simpler to implement but introduces latencyMost production setups combine pattern 1 (completions via webhook) with pattern 2 (report retrieval on demand) and pattern 3 (nightly reconciliation for missed events).
| Method | Typical use case | Migration consideration |
|---|---|---|
| OAuth 2.0 client credentials | Server-to-server score sync, report retrieval | Rotate client_id + client_secret before cutover; document required scopes explicitly |
| OAuth 2.0 authorization code | Recruiter-authenticated report views | Test token refresh flows in staging before cutover |
| API key | Webhook registration, simple REST pulls | Rotate key in both ATS and assessment platform config simultaneously; do not use old key post-cutover |
| Signed webhook secret (HMAC-SHA256) | Webhook payload verification | Regenerate secret; update ATS webhook handler before activating new endpoint |
Credential rotation checklist:
assessments:read, reports:read, candidates:write)The following examples use placeholder base URLs and field names. Replace with your provider's actual API contract.
POST /api/v1/invitations (create invite with idempotency key)
POST /api/v1/invitations HTTP/1.1
Host: assessment.provider.example
Authorization: Bearer {access_token}
Content-Type: application/json
Idempotency-Key: inv_ATS78432_asmt_cognitive_v3_20251002
{
"ats_candidate_id": "ATS-78432",
"email": "[email protected]",
"assessment_id": "asmt_cognitive_v3",
"locale": "en-US",
"expires_at": "2025-10-09T23:59:00+00:00",
"callback_url": "https://your-ats.example/webhooks/assessment"
}
GET /api/v1/assessment-sessions/{session_id}/results
GET /api/v1/assessment-sessions/sess_9f2e1c3d/results HTTP/1.1
Host: assessment.provider.example
Authorization: Bearer {access_token}
Accept: application/json
POST /webhooks/assessment-complete (inbound payload to your ATS endpoint)
{
"event_id": "evt_a1b2c3d4",
"event_type": "ASSESSMENT_COMPLETED",
"session_id": "sess_9f2e1c3d",
"ats_candidate_id": "ATS-78432",
"completed_at": "2025-10-02T09:48:00+00:00",
"report_url": "https://assessment.provider.example/reports/sess_9f2e1c3d",
"composite_score": 74.2,
"recommendation": "ADVANCE"
}
PATCH /ats/v2/candidates/{ats_candidate_id}/attributes (write score back to ATS)
PATCH /ats/v2/candidates/ATS-78432/attributes HTTP/1.1
Host: your-ats.example
Authorization: Bearer {ats_access_token}
Content-Type: application/json
{
"assessment_session_id": "sess_9f2e1c3d",
"assessment_version": "3.2.1",
"scoring_model_version": "sm_2025.2",
"composite_score": 74.2,
"composite_percentile": 76,
"recommendation": "ADVANCE",
"score_synced_at": "2025-10-02T10:01:00+00:00"
}
Idempotency-Key on all POST /invitations and POST /sessions requests. The key should encode enough context to be unique per candidate+assessment combination (e.g., inv_{ats_candidate_id}_{assessment_id}_{date}).event_id + session_id. Store processed event_id values in a dedup table with a TTL of at least 72 hours.completed_at field will corrupt funnel analytics, particularly time-to-next-stage calculations.Before running any test cases, confirm you have:
Never test cutover procedures against production data. Specifically, don't backfill historical scores into the production ATS until the staging run has passed all verification gates.
Run these seven test types in order. Each must pass before proceeding to the next.
ats_candidate_id resolves correctly; confirm score fields populate in ATS without overwriting existing data200 OK on both delivery attemptsTC-01: Standard completion flow Trigger: Invite a test candidate via ATS. Expected: Candidate receives invite, completes assessment, ATS webhook endpoint receives ASSESSMENT_COMPLETED event within 60 seconds of completion, ATS candidate record updates with composite_score, recommendation, and report_url. Pass criteria: All three fields populated; no duplicate session created.
TC-02: Idempotent re-invite Trigger: Send the same POST /invitations request twice with identical Idempotency-Key. Expected: Provider returns the same session_id on both requests; only one session exists in the staging platform. Pass criteria: Second request returns 200 or 409 with the original session object; no second session created.
TC-03: Historical backfill with version tagging Trigger: Import 50 historical sessions from the old provider, each tagged with assessment_version and scoring_model_version. Expected: All 50 records resolve to existing ATS candidate records; score fields populate correctly; version tags are visible in ATS reporting filters. Pass criteria: Zero unresolved ats_candidate_id values; no score field is null where source data is non-null.
TC-04: Multilingual assessment run Trigger: Invite a test candidate with locale: nl-NL; complete the assessment. Expected: Report payload returns with locale: nl-NL; report content is in Dutch; ATS receives a report_language field matching the locale. Pass criteria: Report language matches requested locale; no fallback to English without explicit configuration.
TC-05: Consent and retention flag verification Trigger: Create an invitation for a candidate with a specific consent version; complete the assessment; request the report. Expected: Consent acceptance event appears in the audit log with consent_version, consent_given_at, and data_retention_expires_at populated. Pass criteria: All three consent fields present in both the assessment platform audit log and the ATS candidate record.
After the first 48 hours of production cutover, run these verification queries against both the old and new provider logs for the overlapping candidate cohort:
COMPLETED sessions: new platform total must be within 2% of expected based on invite volumecomposite_score distribution: should not shift by more than 0.5 standard deviations vs. the prior 14-day baseline (a larger shift may indicate a scoring model version mismatch, not genuine candidate performance change)report_url accessibility: sample 50 report URLs from the new provider; confirm all are accessible with valid credentialsIf completion rate drops more than the agreed threshold (see KPI guardrails table above), invoke the rollback procedure immediately. Rollback means re-pointing ATS invite triggers to the old provider, not just stopping new invites. Have the rollback owner identified and on call for the first 72 hours post-cutover.
Candidate identifier mismatch. Using email as the primary join key fails when candidates change their email address between the original application and the historical import. Always join on ats_candidate_id. If your current provider never stored ats_candidate_id, build a reconciliation query that joins on email + invite timestamp with a tolerance window, and manually review any records where two or more candidates share an email.
Silent schema drift. Providers update field names and enum values without announcing breaking changes. A field that was status: "complete" becomes status: "COMPLETED" after a minor version bump. Implement JSON Schema validation on all inbound webhook payloads and outbound API responses. Fail loudly on unexpected values rather than silently dropping fields.
Duplicate scoring updates. Without idempotent webhook handling, a retry of a ASSESSMENT_COMPLETED event writes a second score record to the ATS, creating ambiguity in reporting. Store processed event_id values and reject duplicates before any write operation.
OAuth scope errors during cutover. A client credential set up with assessments:write instead of assessments:read will succeed in tests but fail in production when a read-only operation is expected. Document the exact required scopes for each integration pattern and test scope errors explicitly in staging by requesting tokens with intentionally wrong scopes.
Clock skew and timestamp parsing. A provider that returns timestamps in local time without a timezone offset will cause funnel analytics (time-to-next-stage, completion latency) to be wrong by hours. Enforce ISO-8601 with explicit UTC offset across all fields. Add a validation step in your data pipeline that rejects or flags any timestamp string without a timezone component.
Privacy and consent sequencing breaks. When switching providers, your candidate-facing consent flow changes. If the new provider presents a consent prompt at a different point in the assessment journey than the old one did, candidates may encounter an unexpected consent screen and abandon. Map the consent prompt sequence explicitly and test it end-to-end in TC-05. Platforms that apply privacy-by-design, such as those that request consent before showing results and again before sharing them (a pattern Selection Lab uses, per its 2026 documentation), give you a clear model for where consent checkpoints should sit.
Assessment content versioning on import. Importing historical results without assessment_version tags means your ATS reports will show score distributions that silently mix cohorts assessed on different norm groups. Tag every imported record with the three version fields from the schema above.
Broken deep links from ATS. When the ATS stores a direct URL to a candidate's assessment session, those URLs break after switching providers. Audit all ATS candidate records and custom fields that contain assessment platform URLs before go-live. Replace or redirect them as part of the cutover procedure, not after.
Analytics parity failure. Your new provider may emit different event names or calculate drop-off at different funnel stages than your old one. Before finalizing the integration, map old event names to new ones explicitly and confirm that your BI or ATS reporting dashboards produce equivalent KPI calculations. A 27% drop-off reduction KPI (Selection Lab, 2026) is only meaningful if the drop-off calculation is consistent before and after the switch.
When an issue occurs during or after cutover, route it through this chain:
session_id, event_id, request_id)ats_candidate_id, session_id, event_idAssign a named rollback owner before cutover begins. That person has the authority to make the rollback call without additional approval. On cutover day, have the following open and monitored continuously:
When submitting a data export request to your outgoing provider:
COMPLETED sessions vs. all sessions)Migrating digital assessment vendors without disrupting your hiring pipeline is an execution problem, not a strategy problem. The tools and patterns above give you a concrete QA framework for validating that every stage of the funnel, from ATS-triggered invitation through score sync to interview scheduling, works correctly before you commit to cutover. Run the test cases, monitor the KPI guardrails, and keep the rollback owner on call.