The endpoint that will receive data from the Verkada webhook is required to have the following secure configuration:

  1. HTTPS endpoint with SSL certificate signed by Trusted CAs (list)
  2. Handle concurrent requests
  3. Responds in less than 2000 ms
  4. Responds with JSON
  5. HTTP Response codes: 200 for success, 4XX for errors/invalid requests, 500 to try request later
  6. Accepts and responds with JSON

Your webhook can be configured in Organization Settings. You can add a new webhook to Command if you are an organization admin. Navigate to the Verkada API tab under Organization Settings.

22522252

Add the URL where the Verkada Webhook should send the data, and create a shared secret to authenticate the data sent from the Verkada Webhook.

22802280

Verifying Signature

Verkada includes a timestamp in the Verkada-Signature header sent to your endpoints. This allows you to verify the events were sent by Verkada and not by a third party (ex: replay attack). This timestamp is verified as part of the signed payload and cannot be changed by a potential attacker without invalidating the signature.

You can set this up in the following way

  1. Extract the timestamp and signatures from the header
    Split the header, using the | character as the separator, to get a list of tokens. The first token of the string is a timestamp and the second one is the signature.

  2. Prepare the signed_payload string
    The signed_payload string is created by concatenating:

    1. The actual JSON payload (i.e., the request body)
    2. The character |
    3. The timestamp (as a string)
  3. Determine expected signature:
    Compute HMAC with SHA256 hash function. Use shared secret (ask your Org Admin) as the key and signed_payload string as the message.

  4. Compare the signatures:
    Compare the signature (or signatures) in the header to the expected signature. For equality match, compute difference between current and received timestamp and check whether this satisfies your tolerance threshold. To protect against timing attacks, use constant-time string comparison to compare the expected signature to each of the received signatures.

try:
  timestamp, signature = request.headers['Verkada-Signature'].split('|')
except KeyError:
  raise ApiError(message='Missing signature', code=400)
        
# Check if request was sent in last minute
if time.time() - int(timestamp) > 60:
  raise ApiError(message='Expired', code=403)

to_hash = b'|'.join((request.get_data() or b'', timestamp.encode()))
if not compare_digest(signature.encode(), hmac.new(
  shared_secret, msg=to_hash, digestmod=hashlib.sha256
).hexdigest().encode()):
   raise ApiError(message='Invalid signature', code=403)