Skip to main content

Set up Event Stream Distributor via REST API

Introduction

This tutorial describes how to set up an Event Stream Distributor (ESD) exporter via the REST API. An exporter forwards signageOS platform events (device pairing, provisioning recipes, telemetry, connection changes) to your own HTTPS webhook. For the concepts behind ESD and the full list of subscribable events, see Introduction to Event Stream Distributor.

The same setup can also be done in the UI: see Set up Event Stream Distributor in Box.

Prerequisites

All exporter endpoints use your Organization Auth Token in the x-auth header, in the format token_id:token_secret. If you do not have one yet, follow Getting started with REST APIs to create an organization and its token.

Create an exporter

Send a POST to /v1/event-stream/exporter. The exporter starts disabled unless you set enabled: true.

curl -X POST \
https://api.signageos.io/v1/event-stream/exporter \
-H 'Content-Type: application/json' \
-H 'x-auth: 12a15XXX28612d:2e220XXX77745' \
-d '{
"organizationUid": "117b6d8XXXX18ed4c",
"name": "My Webhook Exporter",
"description": "Forwards device connection events to our backend",
"subscribedEventTypes": [
"Device.DeviceConnectionAdded",
"Device.DeviceConnectionDeleted"
],
"config": {
"configType": "webhook",
"url": "https://example.com/webhook",
"auth": {
"authType": "httpSignedBody",
"signing": {
"algorithm": "HS256",
"secret": "my-secret-key-at-least-32-chars!!"
}
}
}
}'
warning

The signing secret must be at least 32 characters. It is write-only: it is used to sign your webhook deliveries but is never returned by any read endpoint, so store it safely on your side.

warning

As a response you will get 201 and, in the Headers, the Location link where you can find the new exporter's details. Save the exporter's uid for later use.

List and count exporters

List all exporters for the organization:

curl -X GET \
https://api.signageos.io/v1/event-stream/exporter \
-H 'x-auth: 12a15XXX28612d:2e220XXX77745'

Each item includes an observability summary (delivered messages, retries, errors, and the last successful delivery time). To get only the total number of exporters:

curl -X GET \
https://api.signageos.io/v1/event-stream/exporter/count \
-H 'x-auth: 12a15XXX28612d:2e220XXX77745'

Get a single exporter

Fetch one exporter by its uid. The single-exporter read also returns the most recent delivery failures under observability.recentFailures.

curl -X GET \
https://api.signageos.io/v1/event-stream/exporter/61d9XXXX2f7a \
-H 'x-auth: 12a15XXX28612d:2e220XXX77745'
{
"uid": "61d9XXXX2f7a",
"organizationUid": "117b6d8XXXX18ed4c",
"name": "My Webhook Exporter",
"description": "Forwards device connection events to our backend",
"enabled": true,
"subscribedEventTypes": [
"Device.DeviceConnectionAdded",
"Device.DeviceConnectionDeleted"
],
"config": {
"configType": "webhook",
"url": "https://example.com/webhook"
},
"createdAt": "2026-07-01T09:25:05.617Z",
"updatedAt": "2026-07-01T09:25:05.617Z",
"observability": {
"exportedMessages": 1200,
"errors": 4,
"retries": 12,
"lastSuccessfulExportAt": "2026-07-01T10:14:00.000Z",
"recentFailures": [
{
"eventId": "0197f2a4-3a5e-7b1c-9d2f-8c4e6a1b3d5f",
"eventType": "Device.DeviceConnectionAdded",
"attempts": 3,
"lastError": "httpStatus=500",
"failedAt": "2026-07-01T10:31:00.000Z"
}
]
}
}
info

The signing secret is not part of the response. Only config.configType and config.url are returned.

Update an exporter

Send a PUT to /v1/event-stream/exporter/{uid} with the fields you want to change. For example, to enable an exporter and change which events it forwards:

curl -X PUT \
https://api.signageos.io/v1/event-stream/exporter/61d9XXXX2f7a \
-H 'Content-Type: application/json' \
-H 'x-auth: 12a15XXX28612d:2e220XXX77745' \
-d '{
"enabled": true,
"subscribedEventTypes": [
"Device.Verification.DevicePaired",
"Device.Verification.DeviceUnpaired"
]
}'

A successful update returns 204 No Content.

Delete an exporter

curl -X DELETE \
https://api.signageos.io/v1/event-stream/exporter/61d9XXXX2f7a \
-H 'x-auth: 12a15XXX28612d:2e220XXX77745'

A successful delete returns 204 No Content.

Receive and verify the webhook

Once the exporter is enabled, each subscribed event is delivered to your url as a signed HTTP POST. The request body (id, kind, type, payload) and the full list of X-SOS-* headers are documented in the Event catalogue; this section covers verifying the signature.

Every delivery carries an X-SOS-Signature header of the form v1=<base64> and an X-SOS-Timestamp in Unix seconds. Verify the signature on every request before trusting the payload: strip the v1= prefix and base64-decode the rest to get the provided signature bytes, recompute the HMAC-SHA256 over the concatenation of the uppercased HTTP method, the delivery URL, the X-SOS-Timestamp value, and the raw request body (with no separators between them), then compare the two in constant time:

const crypto = require('crypto');

const SIGNATURE_VERSION_PREFIX = 'v1=';

// method: 'POST' (deliveries are always POST)
// url: the exact URL the exporter delivers to (your configured config.url)
// rawBody: the exact received body string, verified BEFORE JSON parsing
function isValidSignature(method, url, rawBody, headers, secret) {
const header = headers['x-sos-signature'] ?? '';
if (!header.startsWith(SIGNATURE_VERSION_PREFIX)) {
return false;
}
const provided = Buffer.from(header.slice(SIGNATURE_VERSION_PREFIX.length), 'base64');
const payload = method.toUpperCase() + url + String(headers['x-sos-timestamp']) + rawBody;
const expected = crypto.createHmac('sha256', secret).update(payload).digest();
return expected.length === provided.length && crypto.timingSafeEqual(expected, provided);
}
warning

Verify against the raw request body, not a re-serialized JSON object: re-serializing can change bytes (key order, whitespace) and break the signature. The signed URL is the exporter's configured config.url, and the timestamp is in seconds.

tip

For each event type and its payload, see the Event catalogue. For the concepts, see Introduction to Event Stream Distributor, and for the exporter endpoints, the REST API reference.