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 POST requests

  • 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 POST requests

  • return a 2xx response 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. Every request Bluesonde sends (both the verification challenge and live events) is signed and carries these headers:

  • Content-Type: application/json and User-Agent: bluesonde-webhooks/0.1

  • X-Webhook-Id

    • The unique ID of the webhook event. Matches the id field of the payload envelope. Use it to de-duplicate deliveries, since an event may be retried.
  • X-Webhook-Event

    • The event type. Matches the type field of the payload envelope (e.g. device.observation.created) and denotes the type of data you can expect.
  • X-Webhook-Timestamp

    • The time the event was sent, as a UNIX timestamp in seconds. 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>, where <timestamp> is the X-Webhook-Timestamp value and <raw-body> is the exact, unparsed request 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 timestamp = headers['x-webhook-timestamp'];
const webhookTimestamp = parseInt(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 several types of events you can subscribe to with a webhook: device location changes, device status updates, new observations, and deployment alert lifecycle changes.

Every event is delivered as a JSON envelope with a common shape (id, type, version, createdAt, organizationId, and an event-specific data object). Device events also include a device object (id, name), and deployment alert events include a deployment object (id, name).

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.

See Schema

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.

See Schema

Observations

Event: device.observation.created

Triggered when a device receives a new observation.

See Schema

Deployment Alerts

Events: deployment.alert.triggered, deployment.alert.escalated, deployment.alert.acknowledged, deployment.alert.resolved

Triggered as a deployment alert moves through its lifecycle: raised (triggered), promoted from warning to critical (escalated), acknowledged by a user (acknowledged), or cleared (resolved). Applies to both observed-property threshold alerts and location geofence alerts.

See Schema