Unified.to
Blog

How to build a Candidate Assessment product with Unified.to


April 11, 2024

There are two different ways that you can build a candidate assessment solution. Some ATS providers will allow for an assessment solution to register with them and then call your server with an assessment request. Unfortunately, not many ATS providers support this method, so Unified.to only supports this method that is widely supported.

In the high-paced realm of recruitment, where talent acquisition meets the evolving landscape of HR technology, the need for efficient and robust Candidate Assessment tools has never been more crucial. As companies strive to identify top talent, incorporating candidate assessment software that facilitates critical activities like background checks, skills tests, candidate analytics analysis and more not only expedites the screening process but also ensures a comprehensive and data-driven approach to talent acquisition.

This guide explore how to develop robust and scalable Candidate Assessment software by leveraging third-party integrations through Unified.to's Unified API developer platform to enhance the value of your candidate assessment solution.

Who

You have built a Candidate Assessment solution for recruiters and hiring managers, enabling them to evaluate candidates who are applying for positions within their companies.

Why (your goal)

You need to access your customers' ATS (application tracking systems) to automate the data flow of new applications so that your solution can test and assess their candidates.

What

You will need to integrate seamlessly with the leading ATS solutions utilized by your customers, such as Greenhouse, Lever, SmartRecruiters, SAP SuccessFactors, and more. This will provide your users with a streamlined user experience, ensuring efficient data flow and synchronization between your Candidate Assessment solution and their preferred Applicant Tracking Systems (ATS).

How to add ATS integrations to your product

Before we start, be sure to first read:

Getting Started with Unified

  1. Get an initial list of applications
    1. When the customer authorizes an ATS connection, read the initial set of active applications
      import { UnifiedTo } from '@unified-api/typescript-sdk';
      import { AtsApplication, AtsApplicationStatus } from '@unified-api/typescript-sdk/dist/sdk/models/shared';
      
      const sdk = new UnifiedTo({
          security: {
              jwt: '<YOUR_API_KEY_HERE>',
          },
      });
      
      export async function getApplications(connectionId: string, trigger_status: AtsApplicationStatus, jobId: string, updatedGte?: Date) {
          const applications: AtsApplication[] = [];
          const limit = 100;
          let offset = 0;
      
          while (true) {
              const result = await sdk.ats.listAtsApplications({
                  updatedGte,
                  jobId,
                  offset,
                  limit,
                  connectionId,
              });
      
              const apps = result.atsApplications || [];
      
              applications.push(...apps.filter((application) => application.status === trigger_status));
      
              if (apps.length === limit) {
                  offset += limit;
              } else {
                  break;
              }
          }
      
          return applications;
      }
      
  2. Set up to get updated applications
    1. If you want to use webhooks to get new/updated applications in the future, create a webhook with that connection_id
      import { createHmac } from 'crypto';
      import { UnifiedTo } from '@unified-api/typescript-sdk';
      import { AtsApplication, AtsApplicationStatus, Event, ObjectType, WebhookType } from '@unified-api/typescript-sdk/dist/sdk/models/shared';
      
      const sdk = new UnifiedTo({
          security: {
              jwt: '<YOUR_API_KEY_HERE>',
          },
      });
      
      interface IncomingWebhook {
          id: string;
          created_at: Date;
          updated_at: Date;
          workspace_id: string;
          connection_id: string;
          hook_url: string;
          object_type: TObjectType;
          interval: number;
          checked_at: Date;
          integration_type: string;
          environment: string;
          event: Event;
          runs: string[];
          fields: string;
          webhook_type: WebhookType;
          is_healthy: boolean;
          page_max_limit: number;
      }
      
      interface IWebhookData<T> {
          data: T[]; // The data array will contact an array of specific objects according to the webhook's connection. (eg. CRM Contacts)
          webhook: IncomingWebhook; // The webhook object
          nonce: string; // random string
          sig: string; // HMAC-SHA1(workspace.secret, data + nonce)
          type: 'INITIAL-PARTIAL' | 'INITIAL-COMPLETE' | 'VIRTUAL' | 'NATIVE';
      }
      
      export async function createApplicationsWebhook(connectionId: string, myWebhookUrl: string) {
          const result = await sdk.unified.createUnifiedWebhook({
              webhook: {
                  hookUrl: myWebhookUrl,
                  objectType: ObjectType.AtsApplication,
                  event: Event.Updated,
                  connectionId,
              },
          });
      
          return result.webhook;
      }
      
      export async function handleUnifiedWebhook(incoming: IWebhookData<AtsApplication>, trigger_status: AtsApplicationStatus) {
          if (incoming.webhook.object_type !== 'ats_application') {
              return; // not for us
          }
      
          const sig =
                              createHmac('sha1', process.env.WORKSPACE_SECRET)
                                  .update(JSON.stringify(incoming.data))
                                  .update(String(incoming.nonce))
                                  .digest('base64');
          if (sig !== incoming.sig) {
              return; // Houston, we have a problem... with security
          }
      
          return incoming.data?.filter((application: AtsApplication) => application.status === trigger_status);
      }
      
    2. Alternatively, create a polling schedule to get new/updated applications, which would be similar to the code in 1a)
  3. Filter applications on a specific status
    1. The application has a standardized status across all ATS integrations, so decide which one makes sense for you to trigger the assessment
    2. Application.Status options are NEW REVIEWING SCREENING SUBMITTED FIRST_INTERVIEW SECOND_INTERVIEW THIRD_INTERVIEW BACKGROUND_CHECK OFFERED ACCEPTED HIRED REJECTED WITHDRAWN. Of these, REVIEWING, SCREENING, FIRST_INTERVIEW, SECOND_INTERVIEW, or BACKGROUND_CHECK should be considered.
  4. Email the candidate with the assessment based on the job
    1. Read the candidate from the Application.candidate_id field
      import { UnifiedTo } from '@unified-api/typescript-sdk';
      
      const sdk = new UnifiedTo({
          security: {
              jwt: '<YOUR_API_KEY_HERE>',
          },
      });
      
      export async function readCandidate(connectionId: string, candidateId: string) {
          return await sdk.ats.getAtsCandidate({
              id: candidateId,
              connectionId,
          });
      }
      
    2. Optional: read the job from the Application.job_id field
      import { UnifiedTo } from '@unified-api/typescript-sdk';
      
      const sdk = new UnifiedTo({
          security: {
              jwt: '<YOUR_API_KEY_HERE>',
          },
      });
      
      export async function readJob(connectionId: string, jobId: string) {
          return await sdk.ats.getAtsJob({
              id: jobId,
              connectionId,
          });
      }
      
    3. Determine which test to send to the candidate based on the Job.id, Job.descripton or Job.name fields
  5. Once the candidate has completed the assessment/test, notify the hiring manager and/or recruiter
    1. Read the employee information based on the Job.hiring_managers_ids and/or Job.recruiter_ids fields and use the Employee.emails field to email them the notification
      import { UnifiedTo } from '@unified-api/typescript-sdk';
      
      const sdk = new UnifiedTo({
          security: {
              jwt: '<YOUR_API_KEY_HERE>',
          },
      });
      
      export async function readEmployee(connectionId: string, employeeId: string) {
          return await sdk.hris.getHrisEmployee({
              id: employeeId,
              connectionId,
          });
      }
      
    2. Optional: push back a PDF document into the customer's ATS that is associated with that application
      import { UnifiedTo } from '@unified-api/typescript-sdk';
      import { AtsDocumentType } from '@unified-api/typescript-sdk/dist/sdk/models/shared';
      
      const sdk = new UnifiedTo({
          security: {
              jwt: '<YOUR_API_KEY_HERE>',
          },
      });
      
      export async function createDocument(connectionId: string, applicationId: string, documentUrl: string, filename: string, type: AtsDocumentType) {
          return await sdk.ats.createAtsDocument({
              atsDocument: {
                  applicationId,
                  type,
                  documentUrl,
                  filename,
              },
              connectionId,
          });
      }
      
    3. Optional: update a candidate with certain tags
      import { UnifiedTo } from '@unified-api/typescript-sdk';
      
      const sdk = new UnifiedTo({
          security: {
              jwt: '<YOUR_API_KEY_HERE>',
          },
      });
      
      export async function updateCandidateTags(connectionId: string, candidateId: string, tags: string[]) {
          return await sdk.ats.updateAtsCandidate({
              atsCandidate: {
                  tags,
              },
              id: candidateId,
              connectionId,
          });
      }
      

Keep learning:

One API to integrate them all

Unified.to is a complete solution to streamline your integration development process and power your Candidate Assessment product with critical third-party candidate data. You're reading this article on the Unified.to blog. We're a Unified API developer platform for SaaS customer-facing integrations. We're excited to continue to innovate at Unified.to and solve hard, critical integration-related problems for our customers. If you're curious about our integrations-as-a-service solution, consider signing up for a free account or meet with an integrations expert.

Blog