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",
"company_id": "123",
"job_id": "4567",
"application_id": "10098",
"user_id": "104188",
"application_file_id": "1",
"sent_at": "2018-01-01T12:15:33-05:00",
"expires_at": "2024-08-17T00:00:00-07:00",
"signed_at": "2018-01-01T12:15:33-05:00",
"signature_as_typed": "Bridget Smith",
"signature_url": "https://my_site_url.applicant-tracking.com/offer/offer_letter_id",
"offer_date": "2024-07-17",
"start_date": "Jul 25, 2024",
"pay_rate": "33.00",
"supervisor": "Steven Chinasky",
"created_at": "2018-01-01T12:15:33-05:00",
"updated_at": "2018-01-01T12:15:33-05:00"
}
}
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 amount of time between retries will increase based on the number of attempts: attempts^4
The ATS will attempt a total of 10 retries before our system stops attempting to broadcast.
Retry Attempt | Delay After Previous Attempt | Total Elapsed Time |
---|---|---|
1st Attempt | < 20 seconds after initial action | < 20 seconds |
2nd Attempt | ~1.25 minutes after 1st attempt | ~1.25 minutes |
3rd Attempt | ~2.5 minutes after 2nd attempt | ~3.75 minutes |
4th Attempt | ~5.75 minutes after 3rd attempt | ~9.5 minutes |
5th Attempt | ~12 minutes after 4th attempt | ~21.5 minutes |
6th Attempt | ~24 minutes after 5th attempt | ~45.5 minutes |
7th Attempt | ~45 minutes after 6th attempt | ~1.5 hours |
8th Attempt | ~1.2 hours after 7th attempt | ~2.7 hours |
9th Attempt | ~2 hours after 8th attempt | ~4.7 hours |
10th Attempt | ~3 hours after 9th attempt | ~7.7 hours |
11th Attempt | ~4.5 hours after 10th attempt | ~12.2 hours |
12th Attempt | ~6 hours after 11th attempt | ~18.2 hours |
13th Attempt | ~8 hours after 12th attempt | ~26.2 hours |
14th Attempt | ~10 hours after 13th attempt | ~36.2 hours |
Updated 5 months ago