Webhooks
We'll take you through the steps to setting up a webhook to receive data passively
Webhooks let Bluesonde send event notifications to your system as they happen, so you do not need to continuously poll the API for updates. After setup, your application can listen for events like new observations, device status changes, and location updates at an HTTPS endpoint you control.
Setup
Before you begin, you will need:
-
a Bluesonde account at least the Organization Admin role
-
a publicly reachable HTTPS endpoint
-
a server that can accept
POSTrequests -
logic to read and return a verification challenge during setup
Once you have these prepared, navigate to the Developer Settings page
On that page, navigate to the OAuth Applications tab and create a new OAuth application. Follow the form, checking off the ‘Enable Webhook’ checkbox.
While we’re working to expand this capability, webhooks are currently 1-1 with an OAuth application.
On the form, for ‘Webhook URL’, enter the webhook endpoint that will respond to events. A webhook endpoint is a URL on your server, for example:
https://example.com/webhooks/bluesonde
When you create the endpoint in the developer settings, Bluesonde will use that URL for both verification and future event delivery.
Your endpoint should:
-
accept HTTPS
POSTrequests -
return a
2xxresponse for valid requests -
be available from the public internet
-
be prepared to handle verification before live events begin flowing
Before Bluesonde starts sending live events, your endpoint must prove that it is under your control. To do this, Bluesonde sends a verification challenge to the endpoint you configured. Your server must respond by echoing the challenge value back.
If your endpoint does not echo the challenge correctly, verification will fail and Bluesonde will not treat the endpoint as active.
What Your Endpoint Should Do
When Bluesonde sends the verification request:
-
read the challenge value from the request
-
respond with that same value
-
return a successful HTTP status
Your handler should not transform, wrap, or rename the value. It should return the challenge exactly as received.
Example Verification Behavior
Bluesonde will send a challenge like:
{
"id": <SOME-ID>,
"type": "webhook.endpoint.verified",
"version": "2026-02-20",
"createdAt": "2026-02-20T00:00:00.000Z",
"organizationId": "<SOME-ID>",
"data": {
"endpointId": "<SOME-ID>",
"challenge": "abc123"
}
}
your endpoint should respond with:
{
"challenge": "abc123"
}
Verification Tips
-
Make sure your server is deployed before starting verification.
-
Confirm that your endpoint is reachable from the public internet.
Should verification fail, you can retry at any time by updating the endpoint, or clicking the ‘Retry Verification’ button.
Handling Data
Post webhook creation, you will be prompted to store a signing key. You can use this key to verify that requests coming to your endpoint are sent by Bluesonde. Webhook events sent by Bluesonde will have several headers:
-
X-Endpoint-Id-
This should match the
body.endpointIdfield and is the ID of your webhook
-
-
X-Webhook-Event-
This should match the
body.event_type, this field denotes the type of data you can expect
-
-
X-Webhook-Timestamp-
This is the time the webhook was sent. You will use this value to validate the signature
-
-
X-Webhook-Signature-
This is the value you can validate against with your webhook secret. This value will be of the form
v1=<hmac-signature> -
The signature is computed via a SHA256 hash on the string
<timestamp>.<raw-body>
-
The following Typescript code can provide a good baseline for validating the signature:
import crypto from "crypto";
// tune this value to your preference
// the longer the window the more susceptible to replay attacks
const toleranceSeconds = 60;
const now = Math.floor(Date.now() / 1000);
const webhookTimestamp = parseInt(body.timestamp);
if (isNaN(webhookTimestamp)) {
throw new Error('Invalid timestamp format');
}
if (Math.abs(now - webhookTimestamp) > toleranceSeconds) {
throw new Error(`Webhook timestamp too old. Difference: ${Math.abs(now - webhookTimestamp)} seconds`);
}
if (!receivedSignature.startsWith('v1=')) {
throw new Error('Invalid signature format - missing v1= prefix');
}
const receivedHash = receivedSignature.slice(3); // Remove 'v1='
if (receivedHash.length !== 64) {
throw new Error('Invalid signature format - incorrect length');
}
// compute the expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`, 'utf8')
.digest('hex');
// use timing-safe comparison to prevent timing attacks
const receivedBuffer = Buffer.from(receivedHash, 'hex');
const expectedBuffer = Buffer.from(expectedSignature, 'hex');
if (receivedBuffer.length !== expectedBuffer.length) {
return false;
}
return crypto.timingSafeEqual(receivedBuffer, expectedBuffer);
Events
There are three types of events you can subscribe to with a webhook: location changes, status updates, and new observations.
Location Changes
Event: device.location.changed
Triggered when a device's location is updated. This event fires whenever a device reports a new GPS position.
Status Changed
Event: device.status.changed
Triggered when a device enters or exits a notable state. This event typically fires when a device receives new data that warrants a status change.
Observations
Event: device.observation.created
Triggered when a device receives a new observation.