Webhook API

Introduction

What is a webhook?

A webhook is a way to receive an event notification from a server. When certain events occur in CodeSignal, a JSON payload containing information about the event will be sent via an HTTP POST request to a specified endpoint URL.

What information is needed to create a webhook?

Each webhook needs the following information:
  • Endpoint URL: The URL that will receive POST payloads. This must be a valid URL that returns a 200 HTTP status after receiving each POST. When the webhook is created or updated, the URL will be POSTed with an empty payload, which must return a 200 status code within 10 seconds in order to successfully complete creating or updating the webhook.
  • Event types: The list of events the webhook will be notified about.
  • Secret key: (optional) A string used to generate a signature header that you can use to verify that the webhook data came from CodeSignal. The secret key is used in conjunction with the webhook's payload to generate a digital signature. Although it is not strictly required, we strongly encourage use of the secret key to verify that webhooks are actually coming from our platform.
  • Owner emails: (optional) If the webhook cannot deliver its payload to the endpoint URL successfully, we will send a notification email to every provided email address. Read more about the retry policy below.
  • Custom headers: (optional) Custom HTTP request headers that we will send to your endpoint in addition to the signature header.

How do I create a webhook?

If you're not already using CodeSignal, start a conversation with us by requesting a demo.

If you're an existing customer, you can create, edit and view status of your webhooks from the Manage Webhooks page.
  • Go to the Manage Webhooks page to create a webhook, and hit the "add a webhook" button. Manage Webhooks page
  • Fill out all of the required fields, as well as optional ones if you need them. We will generate a Secret key automatically, but you can overwrite it with a custom one. Create webhook modal
  • All newly created webhooks are initially active. Healthy webhook
  • A webhook that fails 25 times becomes "disabled". Disabled webhook
  • You can re-enable a webhook by updating its endpoint with a valid one.

Usage

Authentication

For webhooks with a secret key, CodeSignal will sign every request with a X-CodeSignal-Signature HTTP header, generated using the following algorithm:

getSignature(secretKey, endpointUrl, { eventType = '', triggeredOn = '' }) {
  const plainText = `${endpointUrl}${eventType}${triggeredOn}`;
  const hash = crypto.createHmac('sha256', secretKey);
  hash.update(plainText);
  return hash.digest('hex');
}

Here, triggeredOn is the timestamp present in every event payload.

Event handling

For each webhook, we store a list of events the hook should be notified about. When events have been triggered, CodeSignal sends webhooks one event at a time, at a rate of about one per second. If a web hook needs to be notified about multiple events, events are guaranteed to be sent in the order they were triggered.

Retry policy

In the event of a failed webhook (due to timeout, a status code other than 200, or network issues), CodeSignal will attempt a maximum of 25 retries with exponential backoff according to the formula below. The number of seconds to wait is relative to the last failed attempt.
secondsToWait = failCount^4 + 15 + (random(30) * (failCount + 1))

Assuming that random(30) always returns its average of 15, that would lead to the following retry attempts. (Not all retries are listed in the table, since this is just meant to give a sense of the retry backoff timeline.)

Previous failed attempts Time since last attempt Time since first attempt
0 30 seconds 30 seconds
1 46 seconds 1.3 minutes
2 1.2 minutes 2.5 minutes
3 2.6 minutes 5.1 minutes
5 12.2 minutes 23 minutes
10 2.8 hours 7.4 hours
13 8 hours 1 day
15 14 hours 2.1 days
24 3.8 days 20.5 days

CodeSignal will also send an email notification to the owner emails associated with the webhook, if any. To make sure we don't spam you, CodeSignal will send at most one email a day for every webhook. This means that you will generally be notified about the first fail, the 14th fail, and every fail starting from the 16th.

When a webhook is unhealthy, events will be queued in the order they were triggered, and the queue will not be flushed until the first event in the queue is able to be processed successfully (by receiving a 200 OK response from the endpoint URL). If you are able to identify and resolve the issue with the endpoint URL, you can open the webhook settings modal, then test and save the fixed webhook in order to mark it as healthy again. Once it is marked as healthy, the webhook will immediately flush its queue of pending events.

Webhook payloads

All webhooks include the following fields in their payloads:
  • event: [string] The type of event
  • triggeredOn: [number] The timestamp of when the event was triggered, represented as number of milliseconds since the Unix epoch.
  • payload: [object] The list of events the webhook will be notified about.

Events for CodeSignal Pre-Screen

Here is an overview of the end to end workflow of events for CodeSignal Pre-Screen.


Diagram of webhooks end to end flow

Company Client workflow


Diagram of webhooks triggered on client flow

companyTestSessionCreated

Fired when a test session is created.

Request format:
{
   event: 'companyTestSessionCreated',
   triggeredOn: number,
   payload: {
     testSessionId: string,
     testId: string,
     testTitle: string,
     candidateEmail: string,
     candidateName: string
   }
 };
Sample request:
{
   event: 'companyTestSessionCreated',
   triggeredOn: 1553720789347,
   payload: {
     testSessionId: 'lehu382hdleh29',
     testId: 'dhey29hcl28hl28',
     testTitle: 'Software Engineer intern',
     candidateEmail: 'jane.doe@gmail.com',
     candidateName: 'Jane Doe'
   }
 };

Candidate workflow


Diagram of webhooks triggered by the CodeSignal Pre-Screen product

preScreenCandidateDeclined

Fired when a candidate declines a test session invitation.

Request format:
{
  event: 'preScreenCandidateDeclined',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    externalId?: string
  }
};
Sample request:
{
  event: 'preScreenCandidateDeclined',
  triggeredOn: 1553720789347,
  payload: {
    testSessionId: 'lehu382hdleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'Software Engineer intern',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe'
  }
};

preScreenResultShared

Fired when a candidate shares their existing result with a test session invitation.

Request format:
{
  event: 'preScreenResultShared',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    duration: number,
    score: number,
    maxScore: number | null | undefined,
    plagiarismLevel: number,
    plagiarismLabel: 'none' | 'low' | 'medium' | 'high' | '-',
    url: string,
    codingScore?: number | null, // deprecated; use versionedCodingScore instead
    versionedCodingScore?: {
      version: 'original' | 'codingScore2023',
      value: number,
    },
    externalId?: string
  }
};
Sample request:
{
  event: 'preScreenResultShared',
  triggeredOn: 1553720789347,
  payload: {
    testSessionId: 'lehu382hdleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'Software Engineer intern',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    duration: 3480000, // 58 minutes
    score: 900,
    maxScore: 1000,
    codingScore: 820,
    versionedCodingScore: {
      version: 'codingScore2023',
      value: 575
    },
    plagiarismLevel: 0.5,
    plagiarismLabel: 'medium',
    url: https://app.codesignal.com/test-result/oH3qeBC38oFsf7qB4?accessToken=A7HnBpab4aD7xm7Kp-iPhk2se5Wnxeg7x9GruANLzn
  }
};

preScreenExpired

Fired when a test session invitation cannot be fulfilled within the expiration window. The expiredReason field indicates whether the expiration is due to the test not being taken at all, or whether the test could not be certified by CodeSignal due to possibility/suspicion of cheating. If the latter, a rejectedReasons field will also be provided.

Request format:
{
  event: 'preScreenExpired',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    externalId?: string,
    expiredReason: 'testNotTaken' | 'notCertified',
    rejectedReasons?: Array<string>,
  }
};
Sample request:
{
  event: 'preScreenExpired',
  triggeredOn: 1553720789347,
  payload: {
    testSessionId: 'lehu382hdleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'Software Engineer intern',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    expiredReason: 'notCertified',
    rejectedReasons: ['Unauthorized resource', 'Presence of others'],
  }
};

companyTestSessionStarted

Fired when a candidate starts a test session.

Request format:
{
  event: 'companyTestSessionStarted',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string
  }
};
Sample request:
{
  event: 'companyTestSessionStarted',
  triggeredOn: 1553720789347,
  payload: {
    testSessionId: 'lehu382hdleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'Software Engineer intern',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe'
  }
};

preScreenResultVerificationPending

Fired when a candidate finishes a proctored test session.

Request format:
{
  event: 'preScreenResultVerificationPending',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    duration: number,
    score: number,
    maxScore: number | null | undefined,
    codingScore?: number | null, // deprecated; use versionedCodingScore instead
    versionedCodingScore?: {
      version: 'original' | 'codingScore2023',
      value: number,
    },
    externalId?: string
  }
};
Sample request:
{
  event: 'preScreenResultVerificationPending',
  triggeredOn: 1553720789347,
  payload: {
    testSessionId: 'lehu382hdleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'Software Engineer intern',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    duration: 3480000, // 58 minutes
    score: 900,
    maxScore: 1000,
    codingScore: 820,
    versionedCodingScore: {
      version: 'codingScore2023',
      value: 575
    },
  }
};

companyTestSessionFinished

Fired when a non-proctored test session is finished.

Request format:
{
  event: 'companyTestSessionFinished',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    duration: number, // milliseconds
    score: number,
    maxScore: number,
    codingScore?: number | null, // deprecated; use versionedCodingScore instead
    versionedCodingScore?: {
      version: 'original' | 'codingScore2023',
      value: number,
    },
    plagiarismLevel: number,
    plagiarismLabel: 'none' | 'low' | 'medium' | 'high' | '-',
    url: string,
  }
};
Sample request:
{
  event: 'companyTestSessionFinished',
  triggeredOn: 1553720789347,
  payload: {
    testSessionId: 'lehu382hdleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'Software Engineer intern',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    duration: 3480000, // 58 minutes
    score: 900,
    maxScore: 1000,
    codingScore: 820,
    versionedCodingScore: {
      version: 'codingScore2023',
      value: 575
    },
    plagiarismLevel: 0.5,
    plagiarismLabel: 'medium',
    url: https://app.codesignal.com/test-result/oH3qeBC38oFsf7qB4?accessToken=A7HnBpab4aD7xm7Kp-iPhk2se5Wnxeg7x9GruANLzn
  }
};

CodeSignal Auditor workflow


Diagram of webhooks triggered on CodeSignal auditor flow

preScreenResultNotVerified

Fired when a test session is marked as not verified by CodeSignal. This means that either the session has been flagged for suspicious behavior, or a technical issue has been identified during the session (in which case the candidate will be automatically granted a retake).

Request format:
{
  event: 'preScreenResultNotVerified',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    rejectedReasons?: Array<string>,
  }
};
Sample request:
{
  event: 'preScreenResultNotVerified',
  triggeredOn: 1553720789347,
  payload: {
    testSessionId: 'lehu382hdleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'Software Engineer intern',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    rejectedReasons: ['Incomplete recording', 'Screen not visible],
    }
};

preScreenResultVerified

Fired when a proctored test session is verified by CodeSignal.

Request format:
{
  event: 'preScreenResultVerified',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    duration: number, // milliseconds
    score: number,
    maxScore: number,
    codingScore?: number | null, // deprecated; use versionedCodingScore instead
    versionedCodingScore?: {
      version: 'original' | 'codingScore2023',
      value: number,
    },
    plagiarismLevel: number,
    plagiarismLabel: 'none' | 'low' | 'medium' | 'high' | '-',
    url: string,
  }
};
Sample request:
{
  event: 'preScreenResultVerified',
  triggeredOn: 1553720789347,
  payload: {
    testSessionId: 'lehu382hdleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'Software Engineer intern',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    duration: 3480000, // 58 minutes
    score: 900,
    maxScore: 1000,
    codingScore: 820,
    versionedCodingScore: {
      version: 'codingScore2023',
      value: 575
    },
    plagiarismLevel: 0.5,
    plagiarismLabel: 'medium',
    url: https://app.codesignal.com/test-result/oH3qeBC38oFsf7qB4?accessToken=A7HnBpab4aD7xm7Kp-iPhk2se5Wnxeg7x9GruANLzn
  }
};

Events for CodeSignal Pre-Screen Certify

Pre-Screen Certify webhooks process flow

Diagram of webhooks process flow for CodeSignal Pre-Screen Certify

certificationResultPending

Fired when a candidate finishes a standardized test session, if that session has been autoshared with your company. The certification request is still pending and has not yet been certified by the CodeSignal certification team.

Request format:
{
  event: 'certificationResultPending',
  triggeredOn: number,
  payload: {
    certificationRequestId: string,
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    duration: number, // milliseconds
    score: number,
    maxScore: number,
    codingScore: number,
  }
};
Sample request:
{
  event: 'certificationResultPending',
  triggeredOn: 1553720789347,
  payload: {
    certificationRequestId: 'lehjeh36dleh29',
    testSessionId: '2TgbrRMkBiksAXqEd',
    testId: 'dhey29hcl28hl28',
    testTitle: 'General Coding Assessment',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    duration: 5286253,
    score: 876,
    maxScore: 1300,
    codingScore: 793
  }
};

certificationResultNotCertified

Fired when a candidate's standardized test session is denied certification by the CodeSignal certification team, if that test session has been autoshared with your company. The certification request will remain open even after this, in case of retakes or technical issues, so receiving this webhook is not "final" -- you may subsequently receive a certificationResultShared or certificationRequestExpired event.

Request format:
{
  event: 'certificationResultNotCertified',
  triggeredOn: number,
  payload: {
    certificationRequestId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    rejectedReasons: Array<string>,
  }
};
Sample request:
{
  event: 'certificationResultNotCertified',
  triggeredOn: 1553720789347,
  payload: {
    certificationRequestId: 'lehjeh36dleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'General Coding Assessment',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    rejectedReasons: ['Unauthorized resource', 'Presence of others'],
  }
};

certificationResultShared

Fired when a candidate shares their certification result(s) to a specific certification request, or when the result they agreed to automatically share gets certified.

Request format:
{
  event: 'certificationResultShared',
  triggeredOn: number,
  payload: {
    certificationRequestId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    sharedTestSessions: Array<{ // ordered by codingScore in descending order
      codingScore: number,
      score: number,
      maxScore: number,
      startDate: number,
      finishDate: number,
      duration: number,
      id: string,
      url: string,
    }>
  }
};
Sample request:
{
  event: 'certificationResultShared',
  triggeredOn: 1553720789347,
  payload: {
    certificationRequestId: 'lehjeh36dleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'General Coding Assessment',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    sharedTestSessions: [{
      codingScore: 823,
      score: 1148,
      maxScore: 1300,
      startDate: 1618504200000,
      finishDate: 1618507800000,
      duration: 3600000,
      id: '272bu5Na997EdzmQ8',
      url: https://app.codesignal.com/coding-report/8rRHAnRb3mW6RSfJG-a7RFoj2CESoWCji588zCnDJy/J3y6nqx6C89oMq998?accessToken=5ejNbPLLD79xFGNuQ-cEq43zQPQrQqkg2cn3danTag,
    }, {
      codingScore: 793,
      score: 876,
      maxScore: 1300,
      startDate: 1619195400000,
      finishDate: 1619199000000,
      duration: 3600000,
      id: '2TgbrRMkBiksAXqEd',
      url: https://app.codesignal.com/coding-report/8rRHAnRb3mW6RSfJG-a7RFoj2CESoWCji588zCnDJy/J3y6nqx6C89oMq998?accessToken=5ejNbPLLD79xFGNuQ-cEq43zQPQrQqkg2cn3danTag,
    }]
  }
};

certificationRequestExpired

Fired when a request for a certified test result cannot be fulfilled within the expiration window. The expiredReason field indicates whether the expiration is due to the test not being taken at all, or whether the test could not be certified by CodeSignal due to possibility/suspicion of cheating. If the latter, a rejectedReasons field will also be provided.

Request format:
{
  event: 'certificationRequestExpired',
  triggeredOn: number,
  payload: {
    certificationRequestId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    expiredReason: 'testNotTaken' | 'notCertified',
    rejectedReasons?: Array<string>,
  }
};
Sample request:
{
  event: 'certificationRequestExpired',
  triggeredOn: 1553720789347,
  payload: {
    certificationRequestId: 'lehjeh36dleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'General Coding Assessment',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
    expiredReason: 'notCertified',
    rejectedReasons: ['Unauthorized resource', 'Presence of others'],
  }
};

certificationRequestRejected

Fired when a candidate explicitly declines a request to share a certification result. This means that they neither took the test upon request nor agreed to share any pre-existing results.

Request format:
{
  event: 'certificationRequestRejected',
  triggeredOn: number,
  payload: {
    certificationRequestId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
  }
};
Sample request:
{
  event: 'certificationRequestRejected',
  triggeredOn: 1553720789347,
  payload: {
    certificationRequestId: 'lehjeh36dleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'General Coding Assessment',
    candidateEmail: 'jane.doe@gmail.com',
    candidateName: 'Jane Doe',
  }
};

certificationRequestsMerged

This event is fired in case two Certification Requests get merged together. This can happen if a candidate gets invited to take the same assessment using different email addresses. In this case the newer request will be deleted, and its data will be transferred to the older one.

Request format:
{
  event: 'certificationRequestsMerged',
  triggeredOn: number,
  payload: {
    deletedRequestId: string,
    mergedRequestId: string,
  }
};
Sample request:
{
  event: 'certificationRequestRejected',
  triggeredOn: 1553720789347,
  payload: {
    deletedRequestId: 'lehjeh36dleh29',
    mergedRequestId: 'dhey29hcl28hl28',
  }
};