Using Webhooks

Overview

Webhooks provide a powerful tool for system integrations. They enable an external system to "subscribe" to events that occur within the ATS system, and be sent updates when those subscribed events occur.

How to use Webhooks

Conceptually, here is how a Webhook works and how data is ensured to have transmitted successfully:

  • You will need to create a URL on your server that is able to accept and process a POST request containing JSON data from our system.
  • Using our Webhooks API (https://api.atsanywhere.io/reference/post_webhooks) you will need to provide the URL to our system, along with the event that your system wishes to "listen" for (see list of events below).
  • When that event takes place in our system, we will submit the POST request to your URL with the JSON data structure for that event.
  • Your server is then expected to respond with an HTTP status of 200 or 201, so that our system understands that the data was received successfully. If it was not received successfully, our system will try re-send the data periodically (see the "Webhook Retry Logic" section below).

Webhook Validation

For validation purposes, webhooks will contain two headers: X-Hub-Signature and X-ATS-Signature.

  • X-Hub-Signature is the SHA 256 hash of your API password.
  • X-ATS-Signature is the SHA 256 hash of your API password together with the webhook payload.

Webhook Events

Currently, our system has the capability to broadcast a webhook on the following system events.

  • applicant_new - This event is triggered whenever there is a new applicant
  • applicant_updated - This event is triggered whenever an applicant's profile is updated
  • applicant_document_signed - This event is triggered when the applicant signs onboarding documents.
  • applicant_file_added - This event is triggered whenever a new file is added to an applicant's profile
  • applicant_hired_file_added - This event is triggered whenever a hired applicant has a new file added to their profile.
  • applicant_hired_updated - This event is triggered whenever a hired applicant's record is subsequently updated
  • applicant_marked_as_hired - This event is triggered whenever an applicant is hired
  • applicant_offer_letter_sent - This event is triggered whenever an offer letter is sent to an applicant.
  • applicant_unmarked_as_hired - This event is triggered whenever a hired applicant has their "hired" status revoked within the ATS
  • available_applicant_statuses_updated - This event is triggered whenever a company's list of available statuses for applicants is updated
  • company_registered - This event is triggered whenever a company account is registered. (Note, this only applies to users of our partner API.)
  • company_updated - This event is triggered whenever a company account is updated.
  • eop_new - This event is triggered whenever an EOP/EEOC survey is created
  • job_board_post_failed - This event is triggered whenever a job board has failed to post.
  • job_board_post_posted - This event is triggered whenever a job board has successfully been posted.
  • job_new - This event is triggered whenever there is a new job
  • job_updated - This event is triggered whenever a job is updated
  • job_deleted - This event is triggered whenever a job is deleted
  • note_new - This event is triggered whenever a new note on an applicant is added
  • note_updated - This event is triggered whenever a note on an applicant is updated

Webhook Payloads

All webhook payloads follow a standard pattern, with top-level keys for the event_name and the resource name, and then the resource structure as the value for each named resource.

There is an exception for webhooks relating to deletion events. Such webhook payloads will follow the normal pattern except that RESOURCE_DATA will be limited to the resource's original id, since the resource itself was deleted.

{
  'event_name': "NAME_OF_EVENT",
  'RESOURCE_NAME': "RESOURCE_DATA"  
}

For events that deal with applicant data (applicant_new, applicant_updated, etc.), the payload structure will take this form:

{
  'event_name': "applicant_hired",
  'application': {
    'id': "1",
    'company_id': "1000",
    'company_external_id': null,
    'site_external_id': null,
    'first_name': "John",
    'last_name': "Doe",
    'gender': "m",
    'phone': "(555) 123-4567",
    'email': "[email protected]",
    'address': "1 Main St.",
    'address_2': "Apartment 3",
    'city': "Chicago",
    'state': "IL",
    'zipcode': "60624",
    'job_title': "Widget Maker",
    'job_id': "123",
    'status': "Hired",
    'source': null,
    'rating': 5,
    'applied_at': "2018-01-08T06:34:48-08:00",
    'hired_at': "2018-02-08T09:12:01-08:00",
    'start_date': '2018-03-01',
    'created_at': "2018-01-08T06:34:48-08:00",
    'updated_at': "2018-02-08T09:12:01-08:00",
    'archived_at': null
  }
}

For events that have applicant and file data (applicant_file_added, etc.), the payload structure will take this form:

{
  'event_name': "applicant_file_added",
  'application': {
    'id': "1",
    'company_id': "1000",
    'company_external_id': null,
    'site_id': "2000",
    'site_external_id': null,
    'first_name': "John",
    'last_name': "Doe",
    'gender': "m",
    'phone': "(555) 123-4567",
    'email': "[email protected]",
    'address': "1 Main St.",
    'address_2': "Apartment 3",
    'city': "Chicago",
    'state': "IL",
    'zipcode': "60624",
    'job_title': "Widget Maker",
    'job_id': "123",
    'status': "Applied",
    'source': null,
    'rating': 5,
    'applied_at': "2018-01-28T06:34:48-08:00",
    'hired_at': "2018-02-08T09:12:01-08:00",
    'start_date': '2018-03-01',
    'created_at': "2018-01-28T06:34:48-08:00",
    'updated_at': "2018-02-08T09:12:01-08:00",
    'archived_at': null
  },
  'file': {
    'id': "1",
    'company_id': "1000",
    'site_id': "2000",
    'filename': "BackgroundReport_2018_01_30_11_17_33.pdf",
    'file_type': "Background Check",
    'file_url': "<EXPIRING_DOWNLOAD_URL>",
    'created_at': "2018-01-28T11:17:32-05:00"
  }
}

Webhooks that reference company data (company_registered, company_updated etc.) will have this structure:

{
  'event_name': "company_registered",
  'company': {
    'id': "1",
    'external_id': null,
    'company_name': "Widget Co.",
    'city': "Louisville",
    'state': "KY",
    'country': "USA",
    'plan_name': "hiringthing-trial-reup",
    'plan_status': "active",
    'account_owner_first_name': "Josh",
    'account_owner_last_name': "Smith",
    'account_owner_phone': "(888) 545-1122",
    'account_owner_email': "[email protected]",
    'created_at': "2018-01-01T12:15:33-05:00",
  }
}

For events that have EOP/EOCC Survey Data (eop_new), the payload structure will take this form:

{
  'event_name': "eop_new",
  'eop_survey': {
    'app_id': "123",
    'company_id': "123",
    'sex': "Female",
    'ethnicity': "Asian",
    'veteran': "No answer",
    'disability': "No answer"
  }
}

For events that deal with job data (job_new, job_updated, etc.), the payload structure will take this form:

{
  'event_name': "job_new",
  'job': {
    'id': "1",
    'company_id': "1",
    'hiring_entity_name': "Widget Co.",
    'site_id': null,
    'location_id': "1",
    'title': "Job title",
    'job_code': null,
    'external_id': null,
    'status': "online",
    'description': "<p>Job description</p>",
    'abstract': "Need employee to make widgets.",
    'category': "CAT1",
    'joblink': "http://example-job.com",
    'address': "1 Main St.",
    'city': "Anywhere",
    'state': "IL",
    'zipcode': null,
    'country': "US",
    'active': true,
    'visible': true,
    'posted_at': "2018-01-01T12:15:33-05:00",
    'offlined_at': null,
    'archived_at': null,
    'unrated_apps': 4,
    'total_apps': 4,
    'open_positions_count': 2,
    'tags': [],
    'keywords': [],
    'supports_purchases': true,
    'created_at': "2018-01-01T12:15:33-05:00",
    'updated_at': "2018-01-01T12:15:33-05:00",
  }
}

For events that deal with job board post data (job_board_post_posted, job_board_post_failed), the payload structure will take this form:

{
  "event_name": "job_board_post_posted",
  "job_board_post": {
    "id": "1",
    "user_id": "1",
    "job_board_id": "87",
    "job_id": "1",
    "job_board_name": "ZipRecruiter Daily",
    "job_board_data": {},
    "price": { "amount": "100.00", "currency": "USD" },
    "paid_at": "2022-09-20T13:50:26Z",
    "starts_at": "2022-09-20T13:50:26Z",
    "finishes_at": "2022-09-26T07:00:00Z",
    "active": true,
    "last_payment_attempt_failed": false,
    "recurring": false,
    "recur_duration_in_days": null,
    "next_posting_at": null,
    "products": [],
    "created_at": "2022-09-20T13:50:26Z"
  }
}

For events that deal with note data (note_new, note_updated, etc.), the payload structure will take this form:

{
  'event_name': "note_new",
  'note': {
    'id': "1",
    'company_id': "1",
    'application_id': "1",
    'user_id': "1",
    'note_type': "applicant hired",
    'note': "This is a sample body of a note.",
    'protected': false,
    'created_at': "2018-01-01T12:15:33-05:00",
    'updated_at': "2018-01-01T12:15:33-05:00",
  }
}

For events that reference signed document data (applicant_document_signed ), the payload structure will take this form:

{
  'event_name': "applicant_file_added",
  'application': {
    'id': "1",
    'company_id': "1000",
    'company_external_id': null,
    'site_id': "2000",
    'site_external_id': null,
    'first_name': "John",
    'last_name': "Doe",
    'gender': "m",
    'phone': "(555) 123-4567",
    'email': "[email protected]",
    'address': "1 Main St.",
    'address_2': "Apartment 3",
    'city': "Chicago",
    'state': "IL",
    'zipcode': "60624",
    'job_title': "Widget Maker",
    'job_id': "123",
    'status': "Applied",
    'source': null,
    'rating': 5,
    'applied_at': "2018-01-28T06:34:48-08:00",
    'hired_at': "2018-02-08T09:12:01-08:00",
    'start_date': '2018-03-01',
    'created_at': "2018-01-28T06:34:48-08:00",
    'updated_at': "2018-02-08T09:12:01-08:00",
    'archived_at': null
  },
  'signed_document': {
    'id': "11",
    'application_id': "1000",
    'company_id': "1",
    'requestor_id': "2",
    'title': 'Test title',
    'subject': 'Test subject',
    'signed_fields': [
      {
        'name': 'Field1',
        'value': 101
      },
      {
        'name': 'Field2',
        'value': 'John Doe'
      }
    ],
    'created_at': "2018-01-28T11:17:32-05:00",
    'updated_at': "2018-01-28T11:17:32-05:00"
  }
}

For events that reference applicant offer letter data (applicant_offer_letter_sent), the payload structure will take this form:

{
  'event_name': "applicant_offer_letter_sent",
  'offer_letter': {
    'id': "1",
    'application_id': "1",
    'letter': "We are happy to offer you this",
    'message': "Test message",
    'key': "testkey123",
    'signature_as_typed': null,
    'signed_at': "2018-01-01T12:15:33-05:00",
    'crated_at': "2018-01-01T12:15:33-05:00",
    'updated_at': "2018-01-01T12:15:33-05:00",
    'signed_at': "2018-01-01T12:15:33-05:00",
    'app_file_id': "1",
    'expires_at': "2018-01-01T12:15:33-05:00",
    'sent_at': "2018-01-01T12:15:33-05:00",
    'data': {
      'fields': {
        'offer': {
          'start_date': {
            'name': 'Start Date', 
            'value': "2024-05-02", 
            'class': "date"
          }, 
          'offer_date': {
            'name': "Offer Date", 
            'value': "2024-04-17", 
            'class': "date"
          }, 
          'salary': {
            'display_name': "Pay Rate", 
            'name': "Salary", 
            'value': "10000"
          }, 
          'supervisor': {
            'name': "Supervisor", 
            'value': "Billy Joe"
          }
        }
      }
    },
    'manager_id': "1",
    'active_workflow_id': null
  }
}

Registering a webhook

Webhooks can be registered at either a company/client level, or by a Whitelabel partner to be applicable to all of the clients under that Whitelabel.

API documentation for registering/querying/updating webhooks can be found on our technical documentation page here: https://api.atsanywhere.io/reference#webhooks

Webhook Retry Logic

One of the conceptual risks of using webhooks is that the receiving system may not correctly receive the notification that an important system event has taken place. For example, a partner system may be unexpectedly down, or undergoing routine maintenance.

To solve that problem, our ATS system will retry broadcasts until we receive a successful response from the receiving system. The frequency of these retries will decrease according to an exponential retry algorithm (for a conceptual understanding of this, see: https://en.wikipedia.org/wiki/Exponential_backoff).

The ATS will attempt a total of 10 retries, with an exponentially longer and longer period between them before our system stops attempting to broadcast

An example of the retry timing below:

1st Attempt: < 20 seconds after initial action
2nd Attempt: ~1.25 minutes after 1st attempt
3rd Attempt: ~2.5 minutes after 2nd attempt
4th Attempt: ~5.75 minutes after 3rd attempt
5th Attempt: ~12 minutes after 4th attempt
6th Attempt: ~24 minutes after 5th attempt
7th Attempt: ~45 minutes after 6th attempt
8th Attempt: ~1.2 hours after 7th attempt
9th Attempt: ~2 hours after the 8th attempt
10th Attempt: ~3 hours after the 9th attempt