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
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:
- Create a subscription by sending a
POSTto the/sse/subscriptionsendpoint, describing the events you want to receive using one or more subscription filters. - Activate the subscription by sending a
GETto theregistrationUrlreturned 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 Type | Scope Required |
|---|---|
| DEVICE_EVENT | r:devices:* |
| DEVICE_LIFECYCLE_EVENT | r:devices:* |
| DEVICE_HEALTH_EVENT | r:devices:* |
| INSTALLED_APP_LIFECYCLE_EVENT | r:installedapps:* |
| LOCATION_LIFECYCLE_EVENT | r:locations:* |
| MODE_EVENT | r:locations:* |
| SCENE_LIFECYCLE_EVENT | r: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.
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
| Field | Description |
|---|---|
name | A human-readable name for this subscription. |
subscriptionFilters | An 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
| Field | Description |
|---|---|
subscriptionId | Unique identifier for the created subscription. |
registrationUrl | The URL your client must call to activate the subscription and open the event stream. |
version | The SSE event payload version applied to this subscription. Different versions may be specified in the future. |
name | Echoes the subscription name. |
subscriptionFilters | Echoes 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.
| Field | Description |
|---|---|
type | Either LOCATIONIDS or DEVICEIDS. Most integrations use LOCATIONIDS. |
value | An 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. |
eventType | An 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 Type | Description |
|---|---|
DEVICE_EVENT | A device event resulting from a subscription filter matching a device's state change. |
DEVICE_LIFECYCLE_EVENT | A device lifecycle event resulting from a device being created, updated, or deleted. |
DEVICE_HEALTH_EVENT | A device health event resulting from a change in a device's online/offline health status. |
INSTALLED_APP_LIFECYCLE_EVENT | An installed app event resulting from an installed app being created, updated, or deleted. |
LOCATION_LIFECYCLE_EVENT | A location event resulting from a location being created, updated, or deleted. |
MODE_EVENT | Triggered when a location's mode changes. |
SCENE_LIFECYCLE_EVENT | A 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
welcomemessage 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), agoodbyemessage is sent. Your client should treat receipt ofgoodbyeas 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.
| Guardrail | Limit |
|---|---|
| Event filters per subscription | 10 |
| IDs per event filter | 10 |
| Active subscriptions per integration | 2 |
| Active connections per token | 1 |
| API rate limit | 20 calls per minute |
SmartThings reserves the right to block or drop traffic from any client deemed abusive or generating excessive load.
Error Handling
| Response Code | Meaning | Recommended Client Behavior |
|---|---|---|
2xx | Success. | Continue normally. |
4xx (excluding those below) | The request was invalid. | Don't retry without correcting the request; subsequent unchanged requests will still be rejected. |
403 | Either 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. |
409 | A PUT was sent to a subscription that hasn't yet been activated. | Activate the subscription first, then retry the update. |
418 | The client has been flagged as problematic for the event pipeline. | Resolve the underlying client issue before retrying; repeated requests continue to be rejected. |
429 | Too many requests: a rate limit has been exceeded. | Back off exponentially with jitter before retrying; see Best Practices. Don't retry immediately. |
5xx | A 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
- Query and List Devices: retrieve current device state to complement real-time events.
- Control Devices: send commands to devices your subscription is monitoring.
- Troubleshoot Common Errors: general API error handling guidance that applies alongside the SSE-specific codes above.