# Notifications

> Channel + subscription model for delivering ZopNight events to Slack, Teams, Google Chat, and email — with cost-anomaly, budget, and resource-event subscriptions.

Source: https://zop.dev/developer-docs/operations/notifications

---

The notification system lets you receive alerts when resources are started, stopped,
actions fail, overrides fire, retries are exhausted, or budget thresholds are crossed.
Notifications can be delivered to Slack, Microsoft Teams, Google Chat, or email.

The system is built around two concepts: **channels** define where
notifications go (a Slack webhook, an email address, etc.), and
**subscriptions** define which events trigger a notification on a given
channel, optionally scoped to a specific resource, resource group, or cloud account.

## Supported Channels

| Channel | Type ID | Configuration |
| --- | --- | --- |
| **Slack** | `slack` | Incoming webhook URL |
| **Microsoft Teams** | `teams` | Incoming webhook URL |
| **Google Chat** | `gchat` | Incoming webhook URL |
| **Email** | `email` | Recipient email address |

## Legacy Notifications (Deprecated)

**Warning**

The flat `/notifications` endpoints below are deprecated and will be removed
in a future release. They combine channel configuration and event subscriptions into a
single object which limits flexibility. Migrate to the new
**Notification Channels** + **Notification Subscriptions**
APIs documented further down this page.

### List Legacy Notifications

### GET /notifications

List all legacy notification configurations.

### Create Legacy Notification

### POST /notifications

Create a legacy notification configuration.

```bash title="Slack example"
curl -X POST https://zopnight.com/api/notifications \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "slack",
    "webhookURL": "https://hooks.slack.com/services/T00/B00/xxxx",
    "notifyOnStart": true,
    "notifyOnStop": true,
    "notifyOnFailure": true,
    "notifyOnOverride": false
  }'
```

```json title="Response"
{
  "data": {
    "id": "ntf_abc123",
    "channel": "slack",
    "webhookURL": "https://hooks.slack.com/services/T00/B00/xxxx",
    "notifyOnStart": true,
    "notifyOnStop": true,
    "notifyOnFailure": true,
    "notifyOnOverride": false,
    "createdAt": "2025-01-15T10:30:00Z",
    "updatedAt": "2025-01-15T10:30:00Z"
  }
}
```

### Update Legacy Notification

### PUT /notifications/{notificationID}

Update a legacy notification configuration.

```bash title="Request"
curl -X PUT https://zopnight.com/api/notifications/ntf_abc123 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "notifyOnOverride": true
  }'
```

### Delete Legacy Notification

### DELETE /notifications/{notificationID}

Remove a legacy notification configuration.

## Notification Channels

Channels represent a delivery destination such as a Slack workspace, a Teams channel,
or an email address. Create a channel first, then attach subscriptions to it.

### List Channels

### GET /notifications/channels

List all notification channels for the organization.

```bash title="Request"
curl https://zopnight.com/api/notifications/channels \
  -H "Authorization: Bearer <token>"
```

```json title="Response"
{
  "data": [
    {
      "id": "ch_s1a2b3",
      "type": "slack",
      "name": "Engineering Alerts",
      "config": {
        "webhookURL": "https://hooks.slack.com/services/T00/B00/xxxx"
      },
      "createdAt": "2025-06-01T09:00:00Z",
      "updatedAt": "2025-06-01T09:00:00Z"
    }
  ]
}
```

### Create Channel

### POST /notifications/channels

Create a new notification channel.

```bash title="Slack example"
curl -X POST https://zopnight.com/api/notifications/channels \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "slack",
    "name": "Engineering Alerts",
    "config": {
      "webhookURL": "https://hooks.slack.com/services/T00/B00/xxxx"
    }
  }'
```

```json title="Response"
{
  "data": {
    "id": "ch_s1a2b3",
    "type": "slack",
    "name": "Engineering Alerts",
    "config": {
      "webhookURL": "https://hooks.slack.com/services/T00/B00/xxxx"
    },
    "createdAt": "2025-06-01T09:00:00Z",
    "updatedAt": "2025-06-01T09:00:00Z"
  }
}
```

### Update Channel

### PUT /notifications/channels/{channelID}

Update an existing notification channel.

```bash title="Request"
curl -X PUT https://zopnight.com/api/notifications/channels/ch_s1a2b3 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Infra Alerts",
    "config": {
      "webhookURL": "https://hooks.slack.com/services/T00/B00/newtoken"
    }
  }'
```

### Delete Channel

### DELETE /notifications/channels/{channelID}

Delete a notification channel and all its subscriptions.

```bash title="Request"
curl -X DELETE https://zopnight.com/api/notifications/channels/ch_s1a2b3 \
  -H "Authorization: Bearer <token>"
```

**Tip**

ZopNight sends notifications via POST requests to your webhook URL. Ensure your webhook
endpoint is secured and only accepts requests from trusted sources. Consider rotating
webhook URLs periodically as part of your security practices.

## Notification Subscriptions

Subscriptions bind a channel to one or more event types. You can scope a subscription
to the entire organization, a specific cloud account, a resource group, or an individual
resource.

### Event Types

| Event Key | Display Name | Triggered When |
| --- | --- | --- |
| `resource.started` | **Resource Started** | A resource is successfully started by the executor |
| `resource.stopped` | **Resource Stopped** | A resource is successfully stopped by the executor |
| `resource.failed` | **Action Failed** | A start or stop action fails during execution |
| `resource.override` | **Override Triggered** | An override is created for a resource or group |
| `resource.retry_exhausted` | **Retry Exhausted** | All retry attempts for an action have been exhausted |
| `budget.warning` | **Budget Warning** | Spending approaches a configured budget threshold |
| `budget.exceeded` | **Budget Exceeded** | Spending exceeds a configured budget threshold |
| `cost.anomaly.warning` | **Cost Anomaly (Warning)** | Org-level cost exceeds 7-day baseline by 30% |
| `cost.anomaly.critical` | **Cost Anomaly (Critical)** | Org-level cost exceeds 7-day baseline by 100% |
| `cost.anomaly.emergency` | **Cost Anomaly (Emergency)** | Org-level cost exceeds 7-day baseline by 500% |
| `cost.anomaly.cloud_account.*` | **Cloud Account Anomaly** | A cloud account's cost spikes beyond baseline — each * expands to warning, critical, emergency |
| `cost.anomaly.resource_group.*` | **Resource Group Anomaly** | A resource group's cost spikes beyond baseline — each * expands to warning, critical, emergency |
| `cost.anomaly.resource.*` | **Resource Anomaly** | A single resource's cost spikes beyond baseline — each * expands to warning, critical, emergency |
| `cost.anomaly.team.*` | **Team Anomaly** | A team's attributed cost spikes beyond baseline — each * expands to warning, critical, emergency |

### Scope Types

| Scope Type | Scope ID | Description |
| --- | --- | --- |
| `org` | (none) | Matches all events across the organization |
| `cloud_account` | Cloud account ID | Matches events for resources in a specific cloud account |
| `resource_group` | Resource group ID | Matches events for resources in a specific group |
| `resource` | Resource UID | Matches events for a single resource |
| `team` | Team ID | Matches events for resources attributed to a specific team |

### List Subscriptions

### GET /notifications/subscriptions

List all notification subscriptions.

```bash title="Request"
curl https://zopnight.com/api/notifications/subscriptions \
  -H "Authorization: Bearer <token>"
```

```json title="Response"
{
  "data": [
    {
      "id": "sub_x1y2z3",
      "channelID": "ch_s1a2b3",
      "eventType": "resource.stopped",
      "scopeType": "org",
      "scopeID": "",
      "enabled": true,
      "createdAt": "2025-06-01T09:15:00Z",
      "updatedAt": "2025-06-01T09:15:00Z"
    }
  ]
}
```

### Create Subscription

### POST /notifications/subscriptions

Subscribe a channel to an event type.

```bash title="Request"
curl -X POST https://zopnight.com/api/notifications/subscriptions \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "channelID": "ch_s1a2b3",
    "eventType": "resource.failed",
    "scopeType": "cloud_account",
    "scopeID": "acc_prod_aws",
    "enabled": true
  }'
```

```json title="Response"
{
  "data": {
    "id": "sub_f4g5h6",
    "channelID": "ch_s1a2b3",
    "eventType": "resource.failed",
    "scopeType": "cloud_account",
    "scopeID": "acc_prod_aws",
    "enabled": true,
    "createdAt": "2025-06-02T11:00:00Z",
    "updatedAt": "2025-06-02T11:00:00Z"
  }
}
```

### Update Subscription

### PUT /notifications/subscriptions/{subID}

Update an existing subscription.

```bash title="Request"
curl -X PUT https://zopnight.com/api/notifications/subscriptions/sub_f4g5h6 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": false
  }'
```

### Delete Subscription

### DELETE /notifications/subscriptions/{subID}

Delete a notification subscription.

```bash title="Request"
curl -X DELETE https://zopnight.com/api/notifications/subscriptions/sub_f4g5h6 \
  -H "Authorization: Bearer <token>"
```

### Bulk Create / Update Subscriptions

### POST /notifications/subscriptions/bulk

Create or update multiple subscriptions in a single request. Existing subscriptions matched by channelID + eventType + scope are updated; otherwise new ones are created.

```bash title="Request"
curl -X POST https://zopnight.com/api/notifications/subscriptions/bulk \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriptions": [
      {
        "channelID": "ch_s1a2b3",
        "eventType": "resource.started",
        "scopeType": "org",
        "scopeID": "",
        "enabled": true
      },
      {
        "channelID": "ch_s1a2b3",
        "eventType": "resource.stopped",
        "scopeType": "org",
        "scopeID": "",
        "enabled": true
      },
      {
        "channelID": "ch_s1a2b3",
        "eventType": "budget.exceeded",
        "scopeType": "cloud_account",
        "scopeID": "acc_prod_aws",
        "enabled": true
      }
    ]
  }'
```

```json title="Response"
{
  "data": {
    "created": 2,
    "updated": 1
  }
}
```

**Info**

A subscription matches an event when the event type equals the subscription's
`eventType` and the event's source falls within the subscription's scope.
An `org`-scoped subscription matches every event of that type. Narrower
scopes (`cloud_account`, `resource_group`, `resource`,
`team`) only match events originating from the specified entity.

**Tip**

Cost anomaly detection only runs for orgs with active `cost.anomaly.*`
subscriptions. Each dimension (cloud account, resource group, resource, team) is
evaluated independently based on its subscriptions. Anomaly alerts include root cause
analysis and an "Investigate in ZopNight" action button. See
[Cost Anomaly Detection](https://zop.dev/docs/operations/anomaly-detection)
for severity thresholds, cooldown rules, and deduplication logic.

## Event Types

### GET /notifications/event-types

List all available event types that can be used in subscriptions.

```bash title="Request"
curl https://zopnight.com/api/notifications/event-types \
  -H "Authorization: Bearer <token>"
```

```json title="Response"
{
  "data": [
    { "key": "resource.started", "name": "Resource Started", "category": "resource" },
    { "key": "resource.stopped", "name": "Resource Stopped", "category": "resource" },
    { "key": "resource.failed", "name": "Action Failed", "category": "resource" },
    { "key": "resource.override", "name": "Override Triggered", "category": "resource" },
    { "key": "resource.retry_exhausted", "name": "Retry Exhausted", "category": "resource" },
    { "key": "budget.warning", "name": "Budget Warning", "category": "budget" },
    { "key": "budget.exceeded", "name": "Budget Exceeded", "category": "budget" },
    { "key": "cost.anomaly.warning", "name": "Cost Anomaly (Warning)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.critical", "name": "Cost Anomaly (Critical)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.emergency", "name": "Cost Anomaly (Emergency)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.cloud_account.warning", "name": "Cloud Account Anomaly (Warning)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.cloud_account.critical", "name": "Cloud Account Anomaly (Critical)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.cloud_account.emergency", "name": "Cloud Account Anomaly (Emergency)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.resource_group.warning", "name": "Resource Group Anomaly (Warning)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.resource_group.critical", "name": "Resource Group Anomaly (Critical)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.resource_group.emergency", "name": "Resource Group Anomaly (Emergency)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.resource.warning", "name": "Resource Anomaly (Warning)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.resource.critical", "name": "Resource Anomaly (Critical)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.resource.emergency", "name": "Resource Anomaly (Emergency)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.team.warning", "name": "Team Anomaly (Warning)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.team.critical", "name": "Team Anomaly (Critical)", "category": "cost_anomaly" },
    { "key": "cost.anomaly.team.emergency", "name": "Team Anomaly (Emergency)", "category": "cost_anomaly" }
  ]
}
```

## Email Preferences

Email preferences control per-event-type opt-in/opt-out for the authenticated user's
email notifications. These are independent of channel subscriptions and apply only to
the built-in email delivery.

### Get Email Preference

### GET /notifications/email-preferences/{eventKey}

Get the email preference for a specific event type.

```bash title="Request"
curl https://zopnight.com/api/notifications/email-preferences/resource.failed \
  -H "Authorization: Bearer <token>"
```

```json title="Response"
{
  "data": {
    "eventKey": "resource.failed",
    "enabled": true,
    "updatedAt": "2025-06-01T12:00:00Z"
  }
}
```

### Update Email Preference

### PUT /notifications/email-preferences/{eventKey}

Enable or disable email notifications for a specific event type.

```bash title="Request"
curl -X PUT https://zopnight.com/api/notifications/email-preferences/resource.failed \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": false
  }'
```

```json title="Response"
{
  "data": {
    "eventKey": "resource.failed",
    "enabled": false,
    "updatedAt": "2025-06-03T08:45:00Z"
  }
}
```

## Share Recommendation

Send a cost-saving recommendation to one or more notification channels so that team
members who may not use the dashboard directly can act on it.

### POST /notifications/share/recommendation/{recID}

Share a recommendation via configured notification channels.

```bash title="Request"
curl -X POST https://zopnight.com/api/notifications/share/recommendation/rec_abc123 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "channelIDs": ["ch_s1a2b3"],
    "message": "This EC2 instance has been idle for 14 days. Consider stopping or downsizing."
  }'
```

```json title="Response"
{
  "data": {
    "sent": 1,
    "failed": 0
  }
}
```
