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
Signing
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'
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
Using golang:
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)
}