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

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: 'john.doe@gmail.com',
    candidateName: 'John Doe'
  }
};

companyTestSessionFinished

Fired when a candidate finishes the test session (including if the session gets force-finished because the time runs out).

Request format:
{
  event: 'companyTestSessionFinished',
  triggeredOn: number,
  payload: {
    testSessionId: string,
    testId: string,
    testTitle: string,
    candidateEmail: string,
    candidateName: string,
    duration: number, // milliseconds
    score: number,
    maxScore: 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: 'john.doe@gmail.com',
    candidateName: 'John Doe',
    duration: 3480000, // 58 minutes
    score: 850,
    maxScore: 1000,
    plagiarismLevel: 0.5,
    plagiarismLabel: 'medium',
    url: https://app.codesignal.com/test-result/oH3qeBC38oFsf7qB4?accessToken=A7HnBpab4aD7xm7Kp-iPhk2se5Wnxeg7x9GruANLzn
  }
};

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: 'john.doe@gmail.com',
    candidateName: 'John 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: 'john.doe@gmail.com',
    candidateName: 'John 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,
      duration: number,
      id: string,
      url: string,
    }>
  }
};
Sample request:
{
  event: 'certificationResultShared',
  triggeredOn: 1553720789347,
  payload: {
    certificationRequestId: 'lehjeh36dleh29',
    testId: 'dhey29hcl28hl28',
    testTitle: 'General Coding Assessment',
    candidateEmail: 'john.doe@gmail.com',
    candidateName: 'John Doe',
    sharedTestSessions: [{
      codingScore: 823,
      score: 1148,
      maxScore: 1300,
      duration: 5045173,
      id: '272bu5Na997EdzmQ8',
      url: https://app.codesignal.com/coding-report/8rRHAnRb3mW6RSfJG-a7RFoj2CESoWCji588zCnDJy/J3y6nqx6C89oMq998?accessToken=5ejNbPLLD79xFGNuQ-cEq43zQPQrQqkg2cn3danTag,
    }, {
      codingScore: 793,
      score: 876,
      maxScore: 1300,
      duration: 5286253,
      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: 'john.doe@gmail.com',
    candidateName: 'John 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: 'john.doe@gmail.com',
    candidateName: 'John Doe',
  }
};