When a supported event occurs, we will send an HTTP POST request to the specified endpoint.
The body of the request will be encoded as json, with the following structure:

  // describes the type of the event
  event: '',
  // the unique identifier for this event
  id: ''
  // when the event occurred, an RFC-3339 formatted timestamp.
  timestamp: '',
  data: {
    // event-specific data...

Supported Events

Webhooks are posted for the following events: 

Device Events

- A new device enrolls

- A device is deleted

- A device is assigned/re-assigned/unassigned

- A device is marked 'Private'

- A device is marked 'User Owned'

Device Issue Events

 - A new issue is detected
 - An issue is ignored
 - An issue is resolved
 - An issue is escalated, either manually or via automatic escalation


Each POST request will include an Authorization header, where the value is the signature of the request's JSON payload. The signature is generated as a SHA256 HMAC hexdigest, signed with the webhook endpoint's secret. This signing secret can be obtained in the webhook settings screen. Note that each webhook endpoint has its own unique signing secret.
Below are a couple simple examples showing how you can verify that the webhook requests have originated from Kolide: 

Using ruby & sinatra

require 'sinatra'
require 'openssl'
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
    puts "Payload signature not verified"
    puts "Expected: #{expected_hmac}"
    puts "  Actual: #{actual_signature}"
    status 400

Using golang: 

package main
import (
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)
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)
Did this answer your question?