Webhooks

Developers

Webhooks

Webhooks are a great way to get notified about important events that occur within Kolide, allowing you to react to them programmatically.

How to Receive Webhooks

To receive webhooks in Kolide, you must register your HTTP(s) endpoint in the Kolide admin UI.

Note:
Only admins with “Full Access” can register and manage webhook endpoints in Kolide.

To register a new endpoint, you can browse directly to the webhooks settings screen, or follow these steps:

  1. Click your user avatar in the upper-right corner of the Kolide UI.
  2. In the dropdown menu, click Settings.
  3. In the menu on the left, click Developers.
  4. In the sub-menu that appears, click Webhooks.
  5. On the next screen, click Add Endpoint.
  6. In the modal that appears, provide the URL of the publicly accessible HTTPS endpoint where Kolide should send webhooks.

  1. Subscribe to the events you wish to receive on this endpoint. For more information about these events please see the Supported Events section in this document.
  2. Click Save

Verifying Webhook Authenticity

Before trusting the contents of a webhook sent by Kolide, you should verify that the webhook is authentic.

Can I verify webhook authenticity based on IP address?
Kolide currently sends all webhooks from any IP addresses originating from the AWS US-East 1 region. Kolide does not reserve specific IP addresses from AWS for our use, and therefore it is NOT safe to verify the authenticity of a webhook by the IP address it originates from. If you need simple way to filter HTTP traffic, see the “Preliminary Verification” section below

In addition to the JSON payload, each HTTP POST request sent to your endpoint will include an HTTP header called Authorization.

Here is an example of a full request from the test event you can send from the Webhooks UI.

POST / HTTP/1.1
Host: example.com
User-Agent: Ruby
Content-Length: 143
Accept: application/json
Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Authorization: c71fd8b49e0736962d16f8410fa0d6f5bb27d7ef35cd1c0b6d4da0b8568921ac
Content-Type: application/json
X-Forwarded-For: 2601:19c:4a00:141e:2852:5759:61:fee7
X-Forwarded-Host: example.com
X-Forwarded-Proto: https

{
  "event": "webhook.test",
  "id": "01HDVY6MBEYST7797YFFH6QK7C",
  "timestamp": "2023-10-28T20:04:28Z",
  "data": {
    "message": "This is a test webhook event"
  }
}

The signature in Authorization is a SHA256 HMAC hexdigest of the JSON payload in the HTTP body, signed with the webhook endpoint’s secret. This signing secret can be obtained in the webhook settings screen.

To obtain the secret, click “Reveal Secret” on the webhooks settings screen.

Note:
Each webhook endpoint has its own unique signing secret. We recommend you store this secret in a safe place along with other shared API secrets.

Preliminary Verification

In addition to the signature in the Authorization header, each webhook request includes a header named X-Kolide-Webhook-Identifier. The value of this header contains a static identifying value unique to the webhook endpoint.

While you should not rely solely on this value to verify the authenticity of the request, this value can be useful to perform preliminary verification of the request. For example, if you need to restrict traffic through a web application firewall (WAF), reading this header can be simpler than verifying the signature of the request body, with the full signature verification still being done by the final endpoint receiving the webhook request.

This identifier can be found in the webhook settings screen along with the signing secret.

Note:
Rotating the webhook signing secret will also change the identifier.

Webhook Verification Code Examples

Verifying Signatures in Ruby

To consume and verify webhooks in Ruby, you can use the following example as a starting point.

require 'sinatra'
require 'openssl'

SIGNING_SECRET = "<YOUR_SIGNING_SECRET>"

post '/echo' do
  data = request.body.read
  actual_signature = request.get_header 'HTTP_AUTHORIZATION'
  expected_hmac = OpenSSL::HMAC.hexdigest('sha256', SIGNING_SECRET, data)
  if expected_hmac == actual_signature
    puts "OK"
    status 200
  else
    puts "Payload signature not verified"
    puts "Expected: #{expected_hmac}"
    puts "  Actual: #{actual_signature}"
    status 400
  end
end

Verifying Signatures in Go

To consume and verify webhooks in Go, you can use the following example as a starting point.

package main
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)
func main() {
    server := signedHandler{signingSecret: "<SIGNING SECRET>"}
    http.HandleFunc("/", server.ServeHTTP)
    log.Fatal(http.ListenAndServe(":8765", nil))
}
type signedHandler struct {
    signingSecret string
}
func (s *signedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        log.Fatal("unable to read body")
    }
  // verify that the signature is correct
    signature := r.Header.Get("Authorization")
    expectedSig, signatureValid := validMac(body, []byte(signature), []byte(s.signingSecret))
    if !signatureValid {
        fmt.Printf("request is not properly signed, expected: %s, got: %s", expectedSig, signature)
    }
    // respond to the request
    fmt.Fprintf(w, "OK")
}

func validMac(message, messageMAC, key []byte) (expected string, match bool) {
    mac := hmac.New(sha256.New, key)
    mac.Write(message)
    expectedMAC := mac.Sum(nil)
    decodedActualMAC, err := hex.DecodeString(string(messageMAC))
    if err != nil {
        log.Fatal("unable to decode hex")
    }
    return hex.EncodeToString(expectedMAC), hmac.Equal(decodedActualMAC, expectedMAC)
}

Supported Events

The following is a complete list of supported events your webhook endpoint can subscribe to.

devices.created

Added On: December 17th, 2019

Description: This event is sent when a device enrolls in Kolide.

{
  "event": "devices.created",
  "id": "01HDVY8MA3CQ48HJ356FP8M2P6",
  "timestamp": "2023-10-28T20:05:33Z",
  "data": {
    "device_id": 1,
    "device_name": "Jasons-MacBook-Pro"
  }
}

devices.registered

Added On: October 27th, 2023

Description: This event is sent when an end-user successfully registers a device.

{
  "event": "devices.registered",
  "id": "01HDVYMVM58H3RMBA3CR16M4PP",
  "timestamp": "2023-10-28T20:12:14Z",
  "data": {
    "device_id": 1,
    "device_name": "Jasons-MacBook-Pro",
    "device_url": "https://api.kolide.com/devices/1",
    "registered_owner": {
      "id": 1,
      "name": "Casper McFadden",
      "email": "ghost@kolide.co",
      "owner_url": "https://api.kolide.com/people/1"
    }
  }
}

devices.destroyed

Added On: December 17th, 2019

Description: This event is sent when a device is removed in Kolide.

{
  "event": "devices.destroyed",
  "id": "01HDW1TX10N3BS6WJYFP2D421D",
  "timestamp": "2023-10-28T21:07:58Z",
  "data": {
    "device_id": 1,
    "device_name": "Jasons-MacBook-Pro-2"
  }
}

issues.new

Added On: October 26th, 2021

Description: This event is sent when a new issue is detected by Kolide.

{
  "event": "issues.new",
  "id": "01HDW0TFR12Q0DJFVY7Y0DBQ0N",
  "timestamp": "2023-10-28T20:50:15Z",
  "data": {
    "issue_id": 2,
    "title": null,
    "check_id": 71,
    "device": {
      "id": 1,
      "name": "Jasons-MacBook-Pro-2",
    },
    "check": {
      "id": 71,
      "name": "macOS Firewall - Require macOS Firewall is Enabled",
      "tags": []
    }
  }
}

issues.resolved

Added On: October 26th, 2021

Description: This event is sent when an issue resolved in Kolide.

{
  "event": "issues.resolved",
  "id": "01HDW1E30VMSHFFP4MXVCSF7BC",
  "timestamp": "2023-10-28T21:00:58Z",
  "data": {
    "issue_id": 2,
    "title": "macOS Firewall is Disabled",
    "device": {
      "id": 1,
      "name": "Jasons-MacBook-Pro-2",
    },
    "check": {
      "id": 71,
      "name": "macOS Firewall - Require macOS Firewall is Enabled",
      "tags": []
    }
  }
}

requests.issue_exemption

Added On: October 27th, 2023

Description: This is event is sent when an end-user requests an exemption for one or many related issues.

{
  "event": "requests.issue_exemption",
  "id": "01HDW1P1BE8PHC7Y6RQTCFBTWR",
  "timestamp": "2023-10-28T21:05:18Z",
  "data": {
    "device_id": 1,
    "device_name": "Jasons-MacBook-Pro-2",
    "person_email": "ghost@kolide.co",
    "message": "I need to keep my device on an old version of macOS because I am a developer testing backwards compatibility.",
    "issues": [
      {
        "issue_id": 1,
        "issue_url": "https://api.kolide.com/issues/1"
      }
    ]
  }
}

requests.registration

Added On: October 27th, 2023

Description: This is event is sent when an end-user requests approval to register a new device.

{
  "event": "requests.registration",
  "id": "01HDW1V4TDE1B3KCAMR8V7Z4S5",
  "timestamp": "2023-10-28T21:08:06Z",
  "data": {
    "device": {
      "id": 2,
      "name": "Jasons-MacBook-Pro-2",
      "url": "https://api.kolide.com/devices/2"
    },
    "requester": {
      "id": 1,
      "email": "ghost@kolide.co",
      "url": "https://api.kolide.com/people/1"
    },
    "message": "I was recently provisioned a new computer to replace my laptop which no longer works."
  }
}