Webhook API (Permissioned) - BETA

🚧

BETA API

The Webhook API is currently in public beta. We invite developers who are willing to review, test, and provide feedback to use these endpoints and help us stabilize and move them out of beta.

By using these endpoints, developers understand that the Webhook API is subject to change at any time and may cause breaking changes to any integration or application using these endpoints. There may be bugs in how the API performs or inaccuracies in the documentation.

Provide feedback and report bugs here.

What are Webhooks?

Webhooks are a tool that allow you to get notified when certain actions occur through payloads sent to a specified URL. They are a great way to stay updated with changes in your company account. Here at BambooHR we allow you to create webhooks to fire when changes are made to the employees. You can specify what fields to monitor and what fields to POST to you when those changes occur. BambooHR supports two different types of webhooks, Global and Permissioned. This page will outline how to create a permissioned webhook through the API.

Requirements

Permissioned Webhooks can be created by anyone with an API key just like any other API endpoint. Keep in mind that to use the webhook endpoints you will be restricted to one access level. Make sure that you have access to all the fields you will need. Access levels are managed by your admin users in Settings and clicking on Access Levels.

If you are a third party company, you will need to work with an employee of the Bamboo client company. Make sure you have the right permissions to access the information you need for your webhook.

Getting monitor fields and Post fields

Before you create a webhook, you'll want to decide what fields to monitor and what fields to post. To see what fields you can monitor you can call this endpoint. Get monitor fields. You will also need to decide what fields to post when changes occur to the monitored fields. To view a list of all fields you can call this endpoint. Get a list of fields

GET: https://api.bamboohr.com/api/gateway.php/{companyDomain}/v1/webhooks/monitor_fields
GET: https://api.bamboohr.com/api/gateway.php/{{companyDomain}}/v1/meta/fields/

Keep in mind that you may not have access to all fields shown. Access will depend on your access levels granted to the user of the API key. Once you have chosen the fields you would like to monitor or post you can access them by using their IDs, or aliases if they are available to the field.

Configuration

Now that you have chosen your fields, we can start to create your webhook. With each chosen field you can specify a name to be passed to your webhook. This is what each field will return under when the webhook is sent to you.

You will also need a url to set where the webhooks will fire to. If you need a temporary url to test your webhook there is a nifty tool you can use at webhook.site.

You can specify a schedule of when you want the webhooks to fire. For example, you could set your webhooks to fire every half an hour by setting minute to 30. You can also limit how often a webhook will fire by setting a maximum number of requests per interval in seconds. Times represents the given number of times the webhook can fire per the given interval in seconds.

🚧

Keep in mind that we have a rate limit maximum of 1 time every 60 seconds.

Example

Now that you have decided on your fields and settings you are ready to call the create endpoint. Here is an example post body sent to the create endpoint. In this example I have created a webhook that uses employee number and first name. Then when changes are made to those fields, the employee number, first name, last name, and job title, are sent with the field names specified to what I am expecting on my end. I have set it to send to a temporary url every 2 minutes. I have also set a limit of 5 times for every 20 minutes.

{
  "name": "My new webhook",
  "monitorFields": [
        "employeeNumber",
        "firstName"
    ],
    "postFields": {
        "employeeNumber": "Employee #",
        "firstName": "First name",
        "lastName": "Last name",
        "17": "Job title"
    },
  "url": "https://example.com/8846d359-da33-42cb-a1ed-b3182c2ef4ec",
  "format": "json",
    "frequency": {
        "minute": 30
    },
    "limit": {
        "times": 5,
        "seconds": 1200
    }
}
{
    "id": "123",
    "name": "My new webhook",
    "created": "2021-10-04 17:10:47",
    "lastSent": null,
    "monitorFields": [
        "employeeNumber",
        "firstName"
    ],
    "postFields": {
        "employeeNumber": "Employee #",
        "firstName": "First name",
        "jobTitleId": "Job title",
        "lastName": "Last name"
    },
    "url": "https://example.com/8846d359-da33-42cb-a1ed-b3182c2ef4ec",
    "format": "json",
    "frequency": {
        "day": "",
        "month": "",
        "hour": "",
        "minute": "2"
    },
    "limit": {
        "times": "5",
        "seconds": "1200"
    },
    "privateKey": "37b26dc8cf1f08435fd0d08779268d77"
}
{
    "errors": {
        "monitorFieldViolations": [
          	{
              id: 636,
              name:"employeeNumber"
            }
        ],
        "postFieldViolations": [
          	{
              id: 636,
              name:"employeeNumber"
            }
        ]
    }
}

As this is a RESTful API, much of the return data will be the same. You'll want to verify that the created webhook matches your intent. This will also be the only time you will receive the private key so be sure to save it. If you ever need another key, you want to create a new webhook and delete the previous one.

If an error does occur, it will be returned under the errors key value with a list of issues found in the request. For field permission errors you will need to reach out to the customer and ask for those fields to be included on the API users access level permissions.

Webhook Data Format

The webhook URL will be sent a POST request with a list of employees triggered by the monitor fields with data you have requested. Employee data will be batched when possible, so that if multiple employees' data has been changed, they will be sent together.

Data will be posted in the JSON or the standard format of an HTML form submission. We recommend the JSON format. The structure will be:

{
	"employees": [
		{
			"changedFields": [
				"Employee #"
			],
			"fields": {
				"Employee #": {
					"value": "358304"
				},
				"First Name": {
					"value": "Sebastian"
				},
				"Last Name": {
					"value": "Hickle"
				},
				"Job Title": {
					"value": null
				}
			},
			"id": "40351"
		},
		{
			"changedFields": [
				"First name"
			],
			"fields": {
				"Employee #": {
					"value": "351881"
				},
				"First Name": {
					"value": "Angelino"
				},
				"Last Name": {
					"value": "Bergstrom"
				},
				"Job Title": {
					"value": "Developer"
				}
			},
			"id": "40546"
		}
	]
}
employees[40351][changedFields][0] = Employee #
employees[40351][Employee #] = 358304
employees[40351][First name] = Sebastian
employees[40351][Last name] = Hickle
employees[40351][Job title] = 

employees[40546][changedFields][0] = First name
employees[40546][Employee #] = 35188
employees[40546][First name] = Angelino
employees[40546][Last name] = Bergstrom
employees[40546][Job title] = Developer
{
  "errors": [
    {
      "companyDomain": "samltest",
      "webhookId": "3",
      "error": "permission denied to the following fields",
      "fields": {
        "636": "Employee #"
      }
    }
  ]
}
errors[0][companyDomain] = samltest
errors[0][webhookId] = 3
errors[0][error] = Permission denied to the following fields
errors[0][fields][636] = Employee #

"changedFields" is an array of fields that were changed. If the field is included in the employee data, it will give the name of the posted field. Otherwise, a field ID will be given, and more information about the field can be looked up using the API.

While this structure will not be changed, you will receive results based on the fields you have chosen to monitor and post. In addition, in the future, we may add more context to this structure, so it is good practice to ignore fields that your webhook does not recognize.

If you monitor a field that belongs to a table, keep in mind that the webhook will be triggered every time the table is updated, even if the field being monitored is not updated. An example would be if you were monitoring the job title field. This belongs to the job info table so you would also be notified if the reporting to field was updated, since it belongs to the same table.

📘

Note: You will need to parse the form submission data

The actual return data for the example above is this:

employees%5B40351%5D%5BchangedFields%5D%5B0%5D=Employee+%23&employees%5B40351%5D%5BEmployee+%23%5D=358304&employees%5B40351%5D%5BFirst+name%5D=Sebastian&employees%5B40351%5D%5BLast+name%5D=Hickle&employees%5B40351%5D%5BJob+title%5D=&employees%5B40546%5D%5BchangedFields%5D%5B0%5D=First+name&employees%5B40546%5D%5BEmployee+%23%5D=35188&employees%5B40546%5D%5BFirst+name%5D=Angelino&employees%5B40546%5D%5BLast+name%5D=Bergstrom&employees%5B40546%5D%5BJob+title%5D=Developer

Security

BambooHR will post to an HTTP or HTTPS url, but HTTPS is recommended. We no longer recommend a URL to include a token (unless a third party requires it).

Instead we recommend using the private secure key provided at creation. This ensures that a request comes from BambooHR. The webhook is secured using SHA-256 HMAC. You will be returned this only on the initial creation of the webhook. If you happen to lose this key, you will need to create a new webhook and delete the previous one.

When the webhook is called, we send a timestamp header (X-BambooHR-Timestamp) and the SHA-256 HMAC signature (X-BambooHR-Signature). Calling the HMAC function specifying SHA-256, the data payload appended with the timestamp, and then the private key. The resulting value can be compared with the signature to verify that the information is coming from BambooHR and is valid.

An example in PHP on how to calculate and verify the HMAC is below. (PHP handles the headers differently, and other language may do the same. Refer to your specific language for details).

<?php
  $validHash = false;
  $data = file_get_contents('php://input');
  $privateKey = getPrivateKey(); //implement getting private key
  $timestamp = isset($_SERVER['HTTP_X_BAMBOOHR_TIMESTAMP']) ? $_SERVER['HTTP_X_BAMBOOHR_TIMESTAMP'] : null;
  $expectedHash = isset($_SERVER['HTTP_X_BAMBOOHR_SIGNATURE']) ? $_SERVER['HTTP_X_BAMBOOHR_SIGNATURE'] : null;
  if (!empty($timestamp) && !empty($expectedHash)) {
    $calculatedHash = hash_hmac('SHA256', $data . $timestamp, $privateKey);
    if (hash_equals($expectedHash, $calculatedHash)) {
      $validHash = true;
    }
  }
?>

Webhook Shortcomings

Webhooks although being very powerful, are not 100% reliable. For various reasons you might not receive a webhook or you might not have been able to process a previously received webhook. There is also a chance that your API users permission change and invalidates your webhooks. In these circumstances we suggest using the other API endpoints to fill in the missing gaps.

There is also the situation to take into account when you lose access to specific users. This can happen for various reasons. If you need to verify who you still have access to you can make use of the custom reports endpoint. In this example call, you will receive a list of all users who you currently have access to.

POST https://{{url}}/api/gateway.php/{{company}}/v1/reports/custom?format=JSON
Content-Type: application/json
Authorization: Basic {{token}} _

{
    "title": "This is my report",
    "filters": {
        "lastChanged": {
            "includeNull": "no",
            "value": "2012-10-17T16:00:00Z"
        }
    },
    "fields": [
        "firstName",
        "lastName"
    ]
}