Notifications
Multi-channel notification system with configurable subscriptions and event filtering
Notifications are an experimental feature. APIs and behavior may change without prior notice.
LaserData Cloud includes a notification system that delivers real-time alerts about deployment events, resource changes, and operational issues to your preferred channels.
How It Works
The notification system operates at two levels:
- Channels define where notifications are sent (Slack, webhook, email, SMS)
- Subscriptions define which events trigger notifications on a channel, with optional scope filtering
Channels can be scoped to a tenant (organization-wide) or a division. Subscriptions are attached to channels and filter events by type and resource scope.
When an event occurs, the dispatch engine checks each enabled channel's subscriptions. If a subscription matches the event type and scope, the notification is delivered to the channel's destination.
Notification Channels
A channel represents a delivery destination. Each channel has a kind (delivery method), a name, and a destination (URL, email address, or phone number).
Channel Kinds
| Kind | Destination Format | Description |
|---|---|---|
slack | HTTPS webhook URL | Sends a formatted message to a Slack channel via incoming webhook |
webhook | HTTPS URL | Sends a JSON payload ({"subject": "...", "body": "..."}) via HTTP POST |
email | Email address | Sends an HTML email with subject and body |
sms | Phone number (+1234567890) | SMS delivery (coming soon) |
Slack and webhook destinations must use HTTPS and cannot target private/internal IP addresses (SSRF protection). Destinations are encrypted at rest.
Channel Scoping
Channels can be created at two levels:
- Tenant-scoped — organization-wide channels available to all divisions
- Division-scoped — channels specific to a division
Channel Settings
Each channel kind supports optional settings:
Slack:
{
"slack": {
"channel": "#alerts",
"username": "LaserData Bot",
"icon_emoji": ":bell:"
}
}Webhook:
{
"webhook": {
"headers": { "Authorization": "Bearer token123" },
"method": "POST"
}
}SMS:
{
"sms": {
"sender_id": "LaserData"
}
}Notification Subscriptions
Subscriptions determine which events trigger notifications on a channel. Each subscription specifies one or more event types and optional scope filters.
Event Types
| Event Type | Description |
|---|---|
| Deployments | |
deployment_created | A new deployment was created |
deployment_initialized | A deployment finished initializing and is ready |
deployment_upgraded | A deployment was upgraded to a new tier or storage |
deployment_deleted | A deployment was deleted |
deployment_certificates_rotated | Deployment TLS certificates were rotated |
deployment_secrets_rotated | Deployment secrets were rotated |
| Invitations | |
invitation_created | A new team invitation was sent |
invitation_accepted | A team member accepted an invitation |
invitation_rejected | A team member rejected an invitation |
| Organization | |
division_created | A division was created |
division_updated | A division was updated |
division_deleted | A division was deleted |
environment_created | An environment was created |
environment_updated | An environment was updated |
environment_deleted | An environment was deleted |
| Health Alerts | |
high_cpu_usage | CPU usage exceeded threshold on a node |
high_memory_usage | Memory usage exceeded threshold on a node |
high_disk_usage | Disk usage exceeded threshold on a node |
node_unreachable | A deployment node is unreachable |
cpu_usage_resolved | CPU usage returned to normal |
memory_usage_resolved | Memory usage returned to normal |
disk_usage_resolved | Disk usage returned to normal |
node_reachable | A previously unreachable node is back online |
| Other | |
certificate_expiring | A TLS certificate is about to expire |
billing_limit_reached | A deployment's spend limit was reached |
Scope Filtering
Each subscription can filter events by resource scope. Scope levels are ANDed together — an event must match all specified scope levels to trigger the notification.
| Scope | Description |
|---|---|
scope_tenant_ids | Only events from these tenants |
scope_division_ids | Only events from these divisions |
scope_environment_ids | Only events from these environments |
scope_deployment_ids | Only events from these deployments |
If a scope array is empty or omitted, that level matches all resources. For example, setting only scope_deployment_ids: [42] delivers notifications for all matching event types on deployment 42, regardless of which division or environment it belongs to.
Subscription Behavior
- If a channel has no subscriptions, all events are delivered to it (default: send all)
- If a channel has subscriptions, only events matching at least one subscription are delivered
- Notifications are throttled per channel + event type + resource to prevent flooding
Permissions
| Scope | Read | Manage |
|---|---|---|
| Tenant | notifications:read | notifications:manage |
| Division | notifications:read | notifications:manage |
manage implies read. See Roles & Permissions for details.
Plan Limits
| Resource | Basic | Pro | Enterprise |
|---|---|---|---|
| Notification channels (per tenant) | 1 | 3 | 10 |
| Subscriptions per channel | 3 | 10 | 50 |
API Reference
Create a Channel (Tenant)
curl -X POST https://api.laserdata.cloud/tenants/{tenant_id}/channels \
-H "ld-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "slack",
"name": "production-alerts",
"destination": "https://hooks.slack.com/services/T00/B00/xxx",
"settings": {
"slack": {
"channel": "#alerts",
"username": "LaserData"
}
},
"remarks": "Primary alerting channel"
}'| Field | Required | Description |
|---|---|---|
channel | Yes | Channel kind: slack, webhook, email, sms |
name | Yes | Unique name (1-100 chars, alphanumeric with -, _, ., :, space) |
destination | Yes | Target URL, email, or phone number (1-1000 chars) |
settings | No | Channel-specific settings (see Channel Settings) |
remarks | No | Notes (max 500 chars) |
Returns 201 Created.
Create a Channel (Division)
curl -X POST https://api.laserdata.cloud/tenants/{tenant_id}/divisions/{division_id}/channels \
-H "ld-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "webhook",
"name": "ops-webhook",
"destination": "https://example.com/webhooks/laserdata"
}'Same request body as tenant-scoped channels.
List Channels
curl "https://api.laserdata.cloud/tenants/{tenant_id}/channels?page=1&results=10&channel=slack" \
-H "ld-api-key: YOUR_API_KEY"Query parameters:
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (optional, default 1) |
results | integer | Results per page (optional, default 10, max 100) |
channel | string | Filter by kind: slack, webhook, email, sms (optional) |
Response:
{
"total_pages": 1,
"total_results": 2,
"page": 1,
"items": [
{
"id": 1,
"owner_kind": "tenant",
"owner_id": 100,
"channel": "slack",
"name": "production-alerts",
"enabled": true,
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-01T10:00:00Z"
}
]
}Division-scoped: GET /tenants/{tenant_id}/divisions/{division_id}/channels
Get Channel Details
curl https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id} \
-H "ld-api-key: YOUR_API_KEY"{
"id": 1,
"owner_kind": "tenant",
"owner_id": 100,
"channel": "slack",
"name": "production-alerts",
"enabled": true,
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-01T10:00:00Z",
"destination": "https://hooks.slack.com/services/T00/B00/xxx",
"settings": {
"slack": {
"channel": "#alerts",
"username": "LaserData"
}
},
"remarks": "Primary alerting channel"
}The detailed response includes destination, settings, and remarks which are omitted in the list response.
Division-scoped: GET /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}
Update a Channel
curl -X PUT https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id} \
-H "ld-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "updated-channel-name",
"destination": "https://hooks.slack.com/services/T00/B00/new",
"enabled": false
}'All fields are optional — include only the fields you want to change. Set enabled to false to temporarily disable a channel without deleting it.
Returns 204 No Content.
Division-scoped: PUT /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}
Delete a Channel
curl -X DELETE https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id} \
-H "ld-api-key: YOUR_API_KEY"Deleting a channel also deletes all its subscriptions. This action is irreversible.
Returns 204 No Content.
Division-scoped: DELETE /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}
Test a Channel
Send a test notification to verify the channel is configured correctly:
curl -X POST https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id}/test \
-H "ld-api-key: YOUR_API_KEY"Sends a test message to the channel's destination. Rate limited to one test per 10 seconds per channel.
Returns 204 No Content.
Division-scoped: POST /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}/test
Create a Subscription
curl -X POST https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id}/subscriptions \
-H "ld-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message_types": ["deployment_created", "deployment_deleted", "node_unreachable"],
"scope_division_ids": [1],
"scope_environment_ids": [10, 20]
}'| Field | Required | Description |
|---|---|---|
message_types | Yes | Non-empty array of event types to subscribe to |
scope_tenant_ids | No | Filter to events from specific tenants |
scope_division_ids | No | Filter to events from specific divisions |
scope_environment_ids | No | Filter to events from specific environments |
scope_deployment_ids | No | Filter to events from specific deployments |
All scope IDs must reference existing resources within the tenant.
Returns 201 Created.
Division-scoped: POST /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}/subscriptions
Set Subscriptions (Replace All)
Atomically replace all subscriptions for a channel in a single transaction:
curl -X PUT https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id}/subscriptions \
-H "ld-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subscriptions": [
{
"message_types": ["deployment_created", "deployment_deleted"],
"scope_division_ids": [1]
},
{
"message_types": ["high_cpu_usage", "high_memory_usage", "high_disk_usage"],
"scope_deployment_ids": [42, 43]
}
]
}'All existing subscriptions for the channel are deleted and replaced with the provided list. The total count must be within the plan's notification_subscriptions_limit.
Returns 204 No Content.
Division-scoped: PUT /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}/subscriptions
List Subscriptions
curl "https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id}/subscriptions?page=1&results=10" \
-H "ld-api-key: YOUR_API_KEY"Query parameters:
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (optional, default 1) |
results | integer | Results per page (optional, default 10, max 100) |
message_type | string | Filter by event type (optional) |
Response:
{
"total_pages": 1,
"total_results": 2,
"page": 1,
"items": [
{
"id": 1,
"channel_id": 100,
"message_types": ["deployment_created", "deployment_deleted"],
"scope_tenants": [{ "id": 1, "name": "Acme Corp" }],
"scope_divisions": [{ "id": 10, "name": "Platform Eng" }],
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-01T10:00:00Z"
}
]
}Scope arrays are omitted when empty. Scope entries include both id and name (resolved from the database).
Division-scoped: GET /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}/subscriptions
Get Subscription Details
curl https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id}/subscriptions/{subscription_id} \
-H "ld-api-key: YOUR_API_KEY"{
"id": 1,
"channel_id": 100,
"message_types": ["deployment_created", "deployment_deleted"],
"scope_tenants": [{ "id": 1, "name": "Acme Corp" }],
"scope_divisions": [{ "id": 10, "name": "Platform Eng" }],
"scope_environments": [{ "id": 100, "name": "Production" }],
"scope_deployments": [{ "id": 42, "name": "prod-cluster" }],
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-01T10:00:00Z"
}Division-scoped: GET /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}/subscriptions/{subscription_id}
Delete a Subscription
curl -X DELETE https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id}/subscriptions/{subscription_id} \
-H "ld-api-key: YOUR_API_KEY"Returns 204 No Content.
Division-scoped: DELETE /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}/subscriptions/{subscription_id}
Get Notification Types
Returns all available notification event types:
curl https://api.laserdata.cloud/notifications/types \
-H "ld-api-key: YOUR_API_KEY"[
{ "type": "deployment_created", "name": "Deployment created" },
{ "type": "deployment_initialized", "name": "Deployment initialized" },
{ "type": "high_cpu_usage", "name": "High CPU usage" },
{ "type": "node_unreachable", "name": "Node unreachable" }
]Browse Notifications
Browse notification history for a channel:
curl "https://api.laserdata.cloud/tenants/{tenant_id}/channels/{channel_id}/notifications?page=1&results=10" \
-H "ld-api-key: YOUR_API_KEY"Query parameters:
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (optional, default 1) |
results | integer | Results per page (optional, default 10, max 100) |
message_type | string | Filter by event type (optional) |
{
"total_pages": 1,
"total_results": 5,
"page": 1,
"items": [
{
"id": 1,
"tenant_id": 100,
"division_id": 10,
"environment_id": 1,
"deployment_id": 42,
"message_type": "deployment_initialized",
"content": "Deployment prod-cluster has been initialized",
"created_at": "2025-06-01T10:30:00Z"
}
]
}Division-scoped: GET /tenants/{tenant_id}/divisions/{division_id}/channels/{channel_id}/notifications