What are Webhooks?

Get notifications when an event occurs

A webhook is a mechanism that allows Samsara to send an HTTP request to your application when an event occurs. Examples of this include geofence alerts and speeding alerts.

To create a webhook you must:

  1. Define the webhook handler in your application
  2. Register the webhook handler with Samsara
  3. Configure alerts to send the webhook notifications

Some Helpful Tools

Here are a couple helpful tools:

  • RequestBin and webhook.site - both of these allow you to inspect webhook notification HTTP payloads. They provide you with a URL and a UI to inspect any HTTP requests made to that URL. You can configure your webhook to use the testing URL provided by one of these tools and then inspect the notifications from Samsara.
  • curl and Postman - both of these tools allow you to send HTTP requests. This is helpful when you are testing your webhook handler. curl is a command-line tool and Postman is a GUI.
  • ngrok allows you to create a public URL that redirects to your localhost. This allows you to test your webhook locally without having to deploy it to a public server. Note: if the ngrok process terminates, the URL will not longer be usable.

Define the webhook handler

Your application should expose a URL endpoint that is capable of receiving an HTTP POST request from Samsara.

Sample Code

The following examples demonstrate how to receive a webhook notification from Samsara. See below for an example of a full webhook payload.

from flask import Flask, request
app = Flask(__name__)

# Using Flask
@app.route('/webhook-handler', methods=['POST'])
def webhook():
    # parse notification JSON
    notification = request.json

    if notification is None: # could not parse request
        return ('', 400)

    try:
        if notification['event']['alertConditionId'] == 'DeviceLocationInsideGeofence':
            # event is a geofence entry alert
            # do something with the alert info
        elif notification['event']['alertConditionId'] == 'DeviceSpeedAboveSpeedLimit':
            # event is a speeding alert
            # do something with the alert info
        # ... handle other types of alerts
    except ValueError as e:
        # request is malformed
        return ('', 400)
    
    return ('', 200)
// This example uses Express to receive webhooks
const app = require('express')();
const bodyParser = require('body-parser');

app.post('/webhook-handler', bodyParser.raw({type: 'application/json'}), (request, response) => {
  let notification;

  try {
    notification = JSON.parse(request.body);
  } catch (err) {
    response.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (notification.event.alertConditionId) {
    case 'DeviceLocationInsideGeofence':
      // The event is a geofence entry alert
      // Do something with the alert info
      break;
    case 'DeviceSpeedAboveSpeedLimit':
      // The event is a speeding alert
      // Do something with the alert info
      break;
    // ... handle other types of alerts
    default:
      // Got something unexpected
      return response.status(400).end();
  }

  // Return a response to acknowledge receipt of the event
  response.json({received: true});
});

app.listen(8000, () => console.log('Running on port 8000'));

Example Webhook Notification

Example webhook event (see Webhook Reference for full details):

POST /webhook-handler HTTP/1.1
Host: your-app.com
User-Agent: Samsara-Webhook/bf126598c478d8f4149f823a3651c44e3a268119
X-Samsara-Timestamp: 1587597109
X-Samsara-Signature: v1=b2d6e7f01a882a8861c2fee2fc00732dd79eaaadc67d0374b95f4bca252f2ec2
X-Samsara-Request: bf72239c-bebd-4c22-b64c-6fc692ef7c46
X-Samsara-Event-Type: Alert
Content-Type: application/json
Content-Length: 700

{
  "eventId": "bf72239c-bebd-4c22-b64c-6fc692ef7c46",
  "eventMs": 1587597109477,
  "eventType": "Alert",
  "event": {
    "alertEventUrl": "https://cloud.samsara.com/o/53729/alerts/incidents/v2/159827/1/212014918732717/1587596963539/link?dl=eyJkZWVwTGlua1R5cGUiOiJBbGVydENvbmRpdGlvblR5cGVfRGV2aWNlTG9jYXRpb25JbnNpZGVHZW9mZW5jZSIsImdyb3VwSWQiOjU0ODY4fQ==",
    "alertConditionDescription": "Vehicle is inside geofence",
    "alertConditionId": "DeviceLocationInsideGeofence",
    "details": "'Little Red' is inside Parent's House.",
    "device": {
      "id": 212014918732717,
      "name": "Little Red",
      "serial": "G9MTH7CNKZ",
      "vin": "JTMBK32V895081147"
    },
    "orgId": 53729,
    "resolved": false,
    "startMs": 1587596963539,
    "summary": "'Little Red' is inside Parent's House."
  }
}

Return a 2XX Status Code Quickly

Samsara expects your application to return a 2XX status code upon the successful receipt of the notification. If your application returns anything other than a 2XX status code, Samsara will interpret this as an error and resend the notification using exponential backoff. Samsara will attempt to send the notification a total of 5 times, after which the notification will not be sent again. If a new alert fires, the Samsara will attempt to send that new notification to the webhook.

It's best to return a 2XX code as soon as you have verified the contents of the alert. Do longer-running processing asynchronously.

📘

Samsara does not support redirects (status codes of 301, etc). You should always return a 2XX status code if you received the webhook successfully.

Register the webhook handler

Webhook handlers are configured in the Samsara dashboard. Go to your organization's Settings page, scroll down, and select Webhooks from the left-side navigation bar:

644

Click the "Add Webhook" button. Give your webhook a name and enter the URL of your application handler that receives the webhook notifications. Click "Create" to finish creating the webhook:

1058

📘

Webhook URLs must use HTTPS.

Custom Headers

You may include up to 5 custom headers when you register your webhook. When Samsara sends an event to your webhook, the HTTP request will contain the custom headers that you configure. These custom headers can be used to satisfy custom security schemes or other types of custom workflows for your server.

Verify Webhook Configuration

The webhook will be listed in the webhooks table along with a secret key and a list of configured alerts that will be sent to the webhook. Note that the alerts will be empty until you configure an alert to send to this webhook.

2566

📘

You can use the "Test" button to send a test ping to your webhook.

Configuring Alerts

In order to send notifications to your webhook, you must configure alerts. You can configure many different types of alerts within Samsara. See the alert knowledge base article to learn how to create and configure alerts.

On the alert configuration page, you can select one of your webhooks to send the notification to. Make sure to click "Save" at the bottom of the page.

1352

Once you've configured the alert, it will show up in the "Configured Alerts" column on the webhooks page. Whenever that alert is triggered, and HTTP request will be sent to your webhook with details about the alert. See the Webhook Reference for details on how webhook notifications are structured.

2578

Webhook Signatures

Samsara provides a signature as part of the webhook HTTP request so that you can verify the request is coming from Samsara. This signature is part of the X-Samsara-Signature HTTP header.

The signature uses the Secret Key provided on the webhook configuration page.

The X-Samsara-Signature header includes a v1= prefix followed by the signature value. The signature is computed with the HMAC SHA-256 algorithm using your secret key and the message: v1:<timestamp>:<body> where <timestamp> is the X-Samsara-Timestamp HTTP header value and <body> is the request body of the webhook notification.

v1 is the only current signature version. Any other prefix to the signature indicates the request is not coming from Samsara.

Here's how you can verify the HTTP request is coming from Samsara. You can find a full code example after the step-by-step tutorial.

Step 1: Decode the secret key

The secret key displayed on the webhook configuration page is Base64 encoded. It must be decoded before use with the HMAC SHA-256 algorithm.

In this example, the secret key is: rGoy+beNph0qGBLj6Aqoydj6SGA=

import base64

secret = base64.b64decode(bytes('rGoy+beNph0qGBLj6Aqoydj6SGA=', 'utf-8'))
const secret = new Buffer.from('rGoy+beNph0qGBLj6Aqoydj6SGA=', 'base64');

Step 2: Extract the timestamp and signature from the headers

The X-Samsara-Timestamp HTTP header contains the timestamp used to generate the signature.

The X-Samsara-Signature HTTP header contains the signature provided by Samsara.

from Flask import flask, request
app = Flask(__name__)

# Using Flask
@app.route('/webhook-url', methods=['POST'])
def webhook():
    try:
        timestamp = request.headers['X-Samsara-Timestamp']
        signature = request.headers['X-Samsara-Signature']
    except KeyError:
        # missing expected headers
        return ('', 400)
    # Continue processing
const app = require('express')();
const bodyParser = require('body-parser');

// Using Express
app.post('/webhook-url', bodyParser.raw({type: 'application/json'}), (request, response) => {
  
    if (!request.header('X-Samsara-Timestamp') || !request.header('X-Samsara-Signature')) {
        console.log(request.headers);
        return response.status(400).end();
    }
    
    let timestamp = request.header('X-Samsara-Timestamp');
    let signature = request.header('X-Samsara-Signature');
    // Continue processing
});

Step 3: Prepare the message for the HMAC SHA-256 algorithm

The message is used by the HMAC SHA-256 algorithm to generate the signature. It takes the form: v1:timestamp:body.

🚧

When concatenating the request body into the HMAC message, it should be concatenated exactly as is it returned in the request. You should not post-process the body to decode any special characters. Doing so will result in an incorrect signature.

prefix = bytes('v1:' + timestamp + ':', 'utf-8')
message = prefix + request.data
let message = `v1:${timestamp}:${request.body}`;

Step 4: Determine the expected signature

Use the HMAC SHA-256 algorithm to compute the expected signature, using the secret key and the prepared message.

The expected signature will consist of the v1= prefix and the output of the HMAC algorithm in hexadecimal format.

import hmac
from hashlib import sha256

h = hmac.new(secret, message, sha256)
expected_signature = 'v1=' + h.hexdigest()
const crypto = require('crypto');

let hmac = crypto.createHmac('sha256', secret).update(message);
let expectedSignature = 'v1=' + hmac.digest('hex');

Step 5: Compare the signatures

If the expected signature does not match the signature of the request, then the request did not come from Samsara and should be ignored.

if expected_signature != signature:
    return ('', 400)
else:
    # continue processing
if (expectedSignature != signature) {
    return response.status(400).end();
} else {
    // continue processing
}

Sample Code

See above for a detailed step-by-step explanation.

In this example, the webhook's secret key is rGoy+beNph0qGBLj6Aqoydj6SGA=

from flask import Flask, request
import hmac
from hashlib import sha256
import base64

app = Flask(__name__)

# Decode the secret key from Base64 encoding
secret = base64.b64decode(bytes('rGoy+beNph0qGBLj6Aqoydj6SGA=', 'utf-8'))

# Using Flask
@app.route('/webhook-url', methods=['POST'])
def webhook():
    # Extract timestamp and signature
    try:
        timestamp = request.headers['X-Samsara-Timestamp']
        signature = request.headers['X-Samsara-Signature']
    except KeyError:
        # missing expected headers
        return ('', 400)

    # Prepare the message to sign
    prefix = 'v1:{0}:'.format(timestamp)
    message = bytes(prefix, 'utf-8') + request.data
    # Determine the expected signature
    h = hmac.new(secret, message, sha256)
    expected_signature = 'v1=' + h.hexdigest()
    # Compare signatures
    if expected_signature != signature:
        return ('', 400)
    
    # Continue processing
    
    return ('', 200)
const app = require('express')();
const bodyParser = require('body-parser');
const crypto = require('crypto');

// Decode the secret key from Base64 encoding
const secret = Buffer.from('rGoy+beNph0qGBLj6Aqoydj6SGA=', 'base64');

app.post('/webhook-url', bodyParser.raw({type: 'application/json'}), (request, response) => {
  
    if (!request.header('X-Samsara-Timestamp') || !request.header('X-Samsara-Signature')) {
        console.log(request.headers);
        return response.status(400).end();
    }
    
    let timestamp = request.header('X-Samsara-Timestamp');
    let signature = request.header('X-Samsara-Signature');

    // Prepare the message to sign
    let message = `v1:${timestamp}:${request.body}`;
    // Determine the expected signature
    let hmac = crypto.createHmac('sha256', secret).update(message);
    let expectedSignature = 'v1=' + hmac.digest('hex');
    // Compare signatures
    if (expectedSignature != signature) {
        return response.status(400).end();
    }

    // Continue processing

    response.json({received: true});
});

app.listen(port, () => console.log(`Running on port ${port}`));

What’s Next