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:
- Define the webhook handler in your application
- Register the webhook handler with Samsara
- 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:

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:

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.

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.

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.

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
message
for the HMAC SHA-256 algorithmThe 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}`));
Updated about 1 year ago