Skip to main content
SmartThings API updates are on the way!
Preview the upcoming API Access Apps experience here, but note that some features described in this section are coming soon. Visit the SmartThings blog to learn more.

Subscribe to Server-Sent Events (SSE)

Server-Sent Events (SSE) give your application a persistent, real-time stream of events from the SmartThings platform. Unlike webhook subscriptions, which require you to host a publicly reachable endpoint that SmartThings calls, your client opens a single long-lived HTTPS connection and SmartThings pushes events to it as they occur. This makes SSE a good fit for applications that act on behalf of one user at a time (desktop apps, home hubs, single-tenant integrations, and similar clients) rather than multi-tenant cloud services.

This guide covers creating, activating, updating, and deleting SSE subscriptions, as well as listening for events and working within the guardrails that keep the event stream healthy for every client.

Plan Requirements​

important

SSE Subscriptions is a paid feature. Access requires an account on a qualifying Developer or Business plan. Requests made without a qualifying plan are rejected with a 403 response. Visit the SmartThings Developer Console to review plan tiers and enable SSE access for your account.

How Server-Sent Event Subscriptions Work​

Opening an SSE connection is a two-step process:

  1. Create a subscription by sending a POST to the /sse/subscriptions endpoint, describing the events you want to receive using one or more subscription filters.
  2. Activate the subscription by sending a GET to the registrationUrl returned in the response from step 1. This opens the actual event stream.

Once a subscription is active, you can:

  • Update it with a PUT, to change which events it matches, without closing the connection.
  • Delete it with a DELETE, to stop receiving events. (If your client closes the connection itself, event delivery also stops.)

Subscriptions are temporary by design. Every subscription has a server-defined, variable-length time-to-live (TTL), and the connection closes automatically once it expires. Your client is responsible for creating a new subscription to keep receiving events when the connection is severed. See Connection Lifecycle for details.

Authentication & Scopes​

Requests to the SSE Subscriptions API must include a bearer token with the sse scope, in addition to any resource-specific scopes needed to read the events you're subscribing to:

Event TypeScope Required
DEVICE_EVENTr:devices:*
DEVICE_LIFECYCLE_EVENTr:devices:*
DEVICE_HEALTH_EVENTr:devices:*
INSTALLED_APP_LIFECYCLE_EVENTr:installedapps:*
LOCATION_LIFECYCLE_EVENTr:locations:*
MODE_EVENTr:locations:*
SCENE_LIFECYCLE_EVENTr:scenes:*

Events are only delivered if the token used to activate the connection (via the registrationUrl) also carries the required scopes for that event, in addition to the sse scope. Ideally, use the same token to create and activate a subscription.

note

All clients interacting with the SSE Subscriptions API must send a human-readable User-Agent header. See the examples below for a suggested format.

Create a Subscription​

Request​

POST https://api.smartthings.com/v1/sse/subscriptions HTTP/1.1
User-Agent: MyIntegration/v1.0.0 (My SmartThings Integration; contact: development@example.com)
Content-Type: application/json
Authorization: Bearer {access_token}

{
"name": "My Location Subscription",
"subscriptionFilters": [
{
"type": "LOCATIONIDS",
"value": ["95efee9b-6073-4871-b5ba-de6642187293"],
"eventType": ["DEVICE_EVENT", "DEVICE_LIFECYCLE_EVENT"]
}
]
}

Request Body Fields​

FieldDescription
nameA human-readable name for this subscription.
subscriptionFiltersAn array of SubscriptionFilter objects describing which events to receive.

Response​

HTTP/1.1 200 OK
Content-Type: application/json

{
"subscriptionId": "33c78a39-9145-4cd9-9b9e-4ae538111e94",
"registrationUrl": "https://api.smartthings.com/filters/33c78a39-9145-4cd9-9b9e-4ae538111e94/activate?integration=true",
"version": 1,
"name": "My Location Subscription",
"subscriptionFilters": [
{
"type": "LOCATIONIDS",
"value": ["95efee9b-6073-4871-b5ba-de6642187293"],
"eventType": ["DEVICE_EVENT", "DEVICE_LIFECYCLE_EVENT"]
}
]
}

Response Fields​

FieldDescription
subscriptionIdUnique identifier for the created subscription.
registrationUrlThe URL your client must call to activate the subscription and open the event stream.
versionThe SSE event payload version applied to this subscription. Different versions may be specified in the future.
nameEchoes the subscription name.
subscriptionFiltersEchoes the filters applied to this subscription.

A newly created subscription is inactive and is automatically deleted if it's not activated within 1 minute.

Activate a Subscription​

Send a GET request to the registrationUrl returned when the subscription was created, using the same access token used to create it.

Request​

GET ${registrationUrl} HTTP/1.1
User-Agent: MyIntegration/v1.0.0 (My SmartThings Integration; contact: development@example.com)
Authorization: Bearer {access_token}
Accept: text/event-stream

Response​

A successful activation returns a 200 and opens the stream. The first message your client receives is always a CONTROL_EVENT welcome message:

event: CONTROL_EVENT
data: welcome

At this point your connection is active. You will begin receiving events matching your subscription filters. See Listening for Events.

Update a Subscription​

Send a PUT to the subscription's endpoint to change its filters without closing the connection.

Request​

PUT https://api.smartthings.com/v1/sse/subscriptions/{subscriptionId} HTTP/1.1
User-Agent: MyIntegration/v1.0.0 (My SmartThings Integration; contact: development@example.com)
Content-Type: application/json
Authorization: Bearer {access_token}

{
"subscriptionFilters": [
{
"type": "LOCATIONIDS",
"value": ["95efee9b-6073-4871-b5ba-de6642187293"],
"eventType": ["DEVICE_EVENT", "DEVICE_LIFECYCLE_EVENT", "LOCATION_LIFECYCLE_EVENT"]
}
]
}

Response​

HTTP/1.1 200 OK
Content-Type: application/json

{
"subscriptionId": "33c78a39-9145-4cd9-9b9e-4ae538111e94",
"registrationUrl": "${registrationUrl}",
"version": 1,
"name": "My Location Subscription",
"subscriptionFilters": [
{
"type": "LOCATIONIDS",
"value": ["95efee9b-6073-4871-b5ba-de6642187293"],
"eventType": ["DEVICE_EVENT", "DEVICE_LIFECYCLE_EVENT", "LOCATION_LIFECYCLE_EVENT"]
}
]
}

Only active subscriptions can be updated. A PUT sent to a subscription that hasn't yet been activated returns a 409 Conflict. There is a small delay (typically well under a second) between an update being accepted and taking effect for your active event stream; see Best Practices for how to design around this.

Delete a Subscription​

Request​

DELETE https://api.smartthings.com/v1/sse/subscriptions/{subscriptionId} HTTP/1.1
User-Agent: MyIntegration/v1.0.0 (My SmartThings Integration; contact: development@example.com)
Authorization: Bearer {access_token}

Response​

HTTP/1.1 200 OK
Content-Type: application/json

{}

Deleting a subscription immediately halts event delivery. Your client should delete subscriptions it no longer needs (for example, when a user signs out or your application is minimized) rather than letting them run until their TTL expires.

Subscription Filters​

Each subscription is made up of one or more SubscriptionFilter objects, which together describe which events the subscription should match.

FieldDescription
typeEither LOCATIONIDS or DEVICEIDS. Most integrations use LOCATIONIDS.
valueAn array of IDs to match, corresponding to the filter type (for example, an array of location IDs for a LOCATIONIDS filter). For LOCATIONIDS, you may pass ["ALL"] to match all values the subscription owner's token has access to.
eventTypeAn array of event type values this filter should match. If empty, all event types available in the API version are included; we recommend to always specify the event types you need.

Example:

{
"type": "LOCATIONIDS",
"value": ["95efee9b-6073-4871-b5ba-de6642187293"],
"eventType": ["DEVICE_EVENT"]
}

Event Types​

Event TypeDescription
DEVICE_EVENTA device event resulting from a subscription filter matching a device's state change.
DEVICE_LIFECYCLE_EVENTA device lifecycle event resulting from a device being created, updated, or deleted.
DEVICE_HEALTH_EVENTA device health event resulting from a change in a device's online/offline health status.
INSTALLED_APP_LIFECYCLE_EVENTAn installed app event resulting from an installed app being created, updated, or deleted.
LOCATION_LIFECYCLE_EVENTA location event resulting from a location being created, updated, or deleted.
MODE_EVENTTriggered when a location's mode changes.
SCENE_LIFECYCLE_EVENTA scene lifecycle event resulting from a scene being created, updated, or deleted.

Listening for Events​

Once a connection is active, events are delivered as standard Server-Sent Events: each message includes an event name and a data payload, encoded as a single line of JSON.

event: DEVICE_EVENT
data: {"eventTime":1536708654731,"eventType":"DEVICE_EVENT","deviceEvent":{"eventId":"ba65aafb-b61a-11e8-9cc3-83b27408aa79","locationId":"95efee9b-6073-4871-b5ba-de6642187293","deviceId":"e16098f0-08bd-4ecb-aecb-9f30902bb618","componentId":"main","capability":"switch","attribute":"switch","value":"on","valueType":"string","stateChange":true,"subscriptionName":"My Location Subscription"}}

The event's JSON data object includes a timestamp of when the event was generated, an eventType field that mirrors the top-level event field, and a matching object (deviceEvent, locationLifecycleEvent, modeEvent) with the details for that event type.

Connection Lifecycle​

  • Activation window: An inactive subscription (created but not yet activated) is automatically deleted after 1 minute.
  • Time-to-live (TTL): Once active, a subscription is assigned a variable-length TTL. Requests to update or delete a subscription after its TTL has elapsed return 403.
  • Keepalives: Periodically, the server will resend the welcome message on the open connection to confirm it's still alive, independent of event delivery.
  • Closing: Before a socket closes for an expected reason (TTL expiration or an explicit DELETE), a goodbye message is sent. Your client should treat receipt of goodbye as a signal to create a new subscription if it wants to keep receiving events.
  • No resume support: SSE connections don't support resuming from a specific event ID after a disconnect. Events generated while a connection is down (including during automatic reconnect attempts) aren't redelivered once the new connection is established. Design your application to reconcile state after reconnecting rather than relying on gapless delivery (see Best Practices).
  • Event delivery: Normally, event delivery is at-least-once and ordering is preserved, but neither is guaranteed. For example, events generated within milliseconds of a subscription being created or updated are at risk of being missed or, less commonly, delivered more than once. See Best Practices for more details.

Guardrails & Rate Limits​

To keep the event pipeline healthy for all clients, the SSE Subscriptions API enforces the following limits. Requests that exceed them are rejected; see Error Handling.

GuardrailLimit
Event filters per subscription10
IDs per event filter10
Active subscriptions per integration2
Active connections per token1
API rate limit20 calls per minute
note

SmartThings reserves the right to block or drop traffic from any client deemed abusive or generating excessive load.

Error Handling​

Response CodeMeaningRecommended Client Behavior
2xxSuccess.Continue normally.
4xx (excluding those below)The request was invalid.Don't retry without correcting the request; subsequent unchanged requests will still be rejected.
403Either the subscription doesn't exist (for example, it's expired), the requesting token doesn't have access to it, or the token doesn't have access to a resource named in a filter.Create a new subscription; don't retry the same request. If you suspect an issue with resource access, check with the REST API to confirm your token still has access to the requested resources.
409A PUT was sent to a subscription that hasn't yet been activated.Activate the subscription first, then retry the update.
418The client has been flagged as problematic for the event pipeline.Resolve the underlying client issue before retrying; repeated requests continue to be rejected.
429Too many requests: a rate limit has been exceeded.Back off exponentially with jitter before retrying; see Best Practices. Don't retry immediately.
5xxA transient server-side issue.Retry with a capped number of attempts and exponential backoff. Avoid tight retry loops, which can worsen the underlying issue.

Best Practices​

Only subscribe to what you need. Always specify explicit eventType values in your filters. An empty eventType array matches every event type available, but may generate a large volume of events, which could lead to slower delivery, dropped events, or having your client rate limited.

Update infrequently. Each PUT may lead to missed events around the time of the update. Batch filter changes rather than issuing frequent small updates.

Use exponential backoff for errors. In exceptional situations, the SSE Subscriptions API may return 429 or 5xx HTTP codes. When it does, your application should back off exponentially (adding a random jitter of ±10–25% is recommended) to avoid being flagged, rate limited, or blocked.

Prefer updating over recreating. Change an existing subscription's filters with a PUT and let the connection persist for its full TTL, rather than tearing it down and recreating it for short-lived operations.

Subscribe before you expect events, not after. There's a short delay between a subscription being created or updated and events beginning to flow to your SSE connection. If you know an event is coming, make sure the relevant filter has been in place for at least a few hundred milliseconds beforehand.

Event delivery and ordering are best-effort, but not guaranteed. Under normal conditions, event delivery is at-least-once and ordering is preserved. However, the event pipeline prioritizes real-time delivery over these characteristics. Integrations that require strict delivery or ordering guarantees should build their own safeguards, such as buffering events and reordering by timestamp, or periodically reconciling state via polling against the API.

Always have a fallback. As noted above, event delivery isn't guaranteed. If your application is expecting an event and doesn't receive it in a reasonable window, fall back to calling the REST API directly to check current state.

Disconnect when inactive. Each open subscription requires bandwidth from your application. Delete subscriptions your application no longer needs (for example, when a user session ends) rather than leaving them open until they expire on their own.

Up Next​