Mapping JobCannon Scores to SmartRecruiters Fields
Technical guide to parsing JobCannon assessment results in SmartRecruiters and mapping dimensions to scorecard fields via the Connect API.
Mapping JobCannon Scores to SmartRecruiters Fields
When a candidate completes a JobCannon assessment in SmartRecruiters, the result is delivered via webhook. This guide explains how to parse and persist the results.
Webhook Payload Structure
SmartRecruiters sends a POST to https://api.jobcannon.io/smartrecruiters/webhooks with assessment results:
{
"event": "candidate.assessment.completed",
"timestamp": "2026-05-17T14:32:00Z",
"candidateId": "cand_abc123",
"assessment": {
"id": "assess_xyz789",
"type": "jobcannon",
"name": "Big Five",
"result": {
"status": "complete",
"partner_score": 72,
"partner_profile_url": "https://app.jobcannon.io/results/[session-id]",
"partner_data": {
"assessment_name": "Big Five",
"assessment_slug": "big-five",
"completed_at": "2026-05-17T14:32:00Z",
"duration_seconds": 245,
"dimensions": [
{
"name": "Openness",
"score": 68,
"percentile": 65,
"interpretation": "above_average"
},
{
"name": "Conscientiousness",
"score": 81,
"percentile": 78,
"interpretation": "high"
}
],
"metadata": {
"candidates_percentile_rank": 65,
"faking_detected": false,
"social_desirability_scale": 32
}
}
}
}
}
Parsing the Assessment Result
Here's a TypeScript example to extract and process assessment results:
interface SmartRecruitersAssessmentPayload {
event: string
timestamp: string
candidateId: string
assessment: {
id: string
name: string
result: {
status: string
partner_score: number
partner_profile_url: string
partner_data: {
assessment_name: string
dimensions: Array<{ name: string; score: number; percentile: number }>
metadata: { faking_detected: boolean; social_desirability_scale: number }
}
}
}
}
async function processSmartRecruitersAssessment(payload: SmartRecruitersAssessmentPayload) {
const { candidateId, assessment } = payload
// Build custom field updates
const customFields = {
jobcannon_assessment: assessment.name,
jobcannon_score: assessment.result.partner_score,
jobcannon_percentile: assessment.result.partner_data.metadata.candidates_percentile_rank,
jobcannon_faking_risk: assessment.result.partner_data.metadata.social_desirability_scale
}
// Map dimensions
assessment.result.partner_data.dimensions.forEach(dim => {
const fieldKey = `jobcannon_${dim.name.toLowerCase().replace(/\s+/g, '_')}`
customFields[`${fieldKey}_score`] = dim.score
customFields[`${fieldKey}_percentile`] = dim.percentile
})
// Update candidate custom fields via API
await updateCandidateFields(candidateId, customFields)
}
Field Mapping to SmartRecruiters
SmartRecruiters custom fields accept string, number, and URL types. Map as follows:
| JobCannon | SmartRecruiters Field | Type ||---|---|---|| partner_score | jobcannon_score | Number (0–100) || percentile | jobcannon_percentile | Number (0–100) || assessment_name | jobcannon_assessment | Text || dimension.score | jobcannon_[dimension]_score | Number (0–100) || dimension.percentile | jobcannon_[dimension]_percentile | Number (0–100) || social_desirability_scale | jobcannon_faking_risk | Number (0–100) || profile_url | jobcannon_profile_link | URL |Create these fields in SmartRecruiters before deployment (Company Admin → Settings → Custom Fields → Candidate).
Updating Candidate Records
After parsing the result, update the candidate via SmartRecruiters API:
PATCH /v1/candidates/{candidateId}
Authorization: Bearer [access-token]
Content-Type: application/json
{
"customFields": {
"jobcannon_assessment": "Big Five",
"jobcannon_score": 72,
"jobcannon_percentile": 65,
"jobcannon_openness_score": 68,
"jobcannon_openness_percentile": 65,
"jobcannon_conscientiousness_score": 81,
"jobcannon_conscientiousness_percentile": 78
}
}
Webhook Verification
Always verify webhook authenticity using the HMAC signature:
import crypto from 'crypto'
function verifySmartRecruitersSignature(
payload: string,
signature: string,
webhookSecret: string
): boolean {
const hash = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex')
return hash === signature
}
// In your webhook handler:
const signature = req.headers['x-smartrecruiters-signature']
const isValid = verifySmartRecruitersSignature(
JSON.stringify(req.body),
signature as string,
webhookSecret
)
if (!isValid) return res.status(401).send('Unauthorized')
Retry and Idempotency
SmartRecruiters retries failed webhooks (5 attempts over 24 hours). Use an idempotency key to prevent duplicate processing:
const idempotencyKey = `sr_${assessment.id}_${timestamp}`
const isProcessed = await checkIdempotencyKey(idempotencyKey)
if (isProcessed) return res.status(200).send('Already processed')
// Process and store idempotency key
await processAssessment(payload)
await storeIdempotencyKey(idempotencyKey)
Handling Faking Detection
If social_desirability_scale > 70, flag for review:
if (assessment.result.partner_data.metadata.social_desirability_scale > 70) {
await updateCandidateFields(candidateId, {
jobcannon_needs_review: true,
jobcannon_review_reason: 'High faking risk. Consider re-testing.'
})
}
EEOC Adverse Impact Analysis
Use percentiles (not raw scores) to check for bias:
const whitePassRate = passCount / totalWhite
const blackPassRate = passCount / totalBlack
if ((blackPassRate / whitePassRate) < 0.8) {
console.warn('Potential adverse impact — investigate')
}
---
**Vendor docs:** https://developers.smartrecruiters.com/reference
**Webhooks:** https://developers.smartrecruiters.com/reference/webhooks
**Custom fields:** https://developers.smartrecruiters.com/reference/custom-fields
**Support:** [email protected]