> ## Documentation Index
> Fetch the complete documentation index at: https://www.seam.co/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Noise Sensors

> Configure noise thresholds, receive verified noise events, and queue an application alert.

Noise sensors measure sound levels in a space. With Seam, your application can configure supported noise thresholds and react to `noise_sensor.noise_threshold_triggered` events. A sound-level event is not an audio recording or a determination that a guest violated a rule.

## What Is a Threshold?

A threshold defines the noise level that triggers a notice during a specified period. For Minut, configure the regular threshold and an optional quiet-hours threshold. Use the property's time zone for daily boundaries. See [threshold configuration](/docs/capability-guides/noise-sensors/configure-noise-threshold-settings) for the provider-specific fields.

Choose thresholds for the actual property, sensor placement and expected background sound. Review false alarms before using a noise event to prompt staff or guests. One universal decibel range is not suitable for every building.

## From a threshold to an alert

```mermaid theme={"dark"}
flowchart TD
  A[Sensor exceeds threshold] --> B[Seam noise event]
  B --> C[Verify webhook signature]
  C --> D[Store one pending alert per event]
  D --> E[Application reviews or notifies staff]
```

1. [Connect a Minut sensor](/docs/device-and-system-integration-guides/minut-sensors/get-started-with-minut-sensors) and confirm it is online.
2. Configure the regular and quiet-hours thresholds your application needs.
3. Subscribe to `noise_sensor.noise_threshold_triggered` using a [webhook](/docs/developer-tools/webhooks).
4. Verify the signature, check the workspace and durably store the event before acknowledging it.
5. Let an application worker decide the next action. Keep event receipt separate from sending a notification.

### Receive and store an alert

This example uses the official Seam SDK and Node.js 24's experimental `node:sqlite` module with a persistent SQLite file. Save it as `noise-receiver.mjs`, install `seam`, and set `SEAM_WEBHOOK_SECRET` and `SEAM_WORKSPACE_ID` from the workspace where you create the webhook. Set `ALERT_DB_PATH` to a persistent disk location. Run `node noise-receiver.mjs` behind an HTTPS reverse proxy that forwards `/webhooks/seam` to port 3000 without changing the request body.

<Accordion title="Complete JavaScript receiver">
  ```javascript JavaScript theme={"dark"}
  import { createServer } from 'node:http'
  import { DatabaseSync } from 'node:sqlite'
  import { SeamWebhook } from 'seam'

  const secret = process.env.SEAM_WEBHOOK_SECRET
  const workspaceId = process.env.SEAM_WORKSPACE_ID
  if (!secret || !workspaceId) throw new Error('Set the webhook secret and workspace ID.')
  const webhook = new SeamWebhook(secret)
  const db = new DatabaseSync(process.env.ALERT_DB_PATH || './noise-alerts.sqlite')
  db.exec(`
    PRAGMA journal_mode = WAL;
    CREATE TABLE IF NOT EXISTS noise_alerts (
      workspace_id TEXT NOT NULL,
      event_id TEXT NOT NULL,
      device_id TEXT NOT NULL,
      occurred_at TEXT NOT NULL,
      payload TEXT NOT NULL,
      status TEXT NOT NULL DEFAULT 'pending',
      PRIMARY KEY (workspace_id, event_id)
    );
  `)
  const insert = db.prepare(`
    INSERT INTO noise_alerts (workspace_id, event_id, device_id, occurred_at, payload)
    VALUES (?, ?, ?, ?, ?)
    ON CONFLICT (workspace_id, event_id) DO NOTHING
  `)

  createServer(async (req, res) => {
    if (req.method !== 'POST' || req.url !== '/webhooks/seam') {
      res.writeHead(404).end()
      return
    }
    let event
    try {
      const chunks = []
      let size = 0
      for await (const chunk of req) {
        size += chunk.length
        if (size > 1024 * 1024) {
          res.writeHead(413).end()
          return
        }
        chunks.push(chunk)
      }
      // Verify the original bytes, before parsing or changing the body.
      event = webhook.verify(Buffer.concat(chunks).toString('utf8'), req.headers)
    } catch {
      console.warn('Rejected webhook verification')
      res.writeHead(400).end()
      return
    }
    if (event.workspace_id !== workspaceId) {
      res.writeHead(403).end()
      return
    }
    if (event.event_type !== 'noise_sensor.noise_threshold_triggered') {
      res.writeHead(204).end()
      return
    }
    if (typeof event.device_id !== 'string' || typeof event.occurred_at !== 'string') {
      res.writeHead(400).end()
      return
    }
    try {
      const result = insert.run(event.workspace_id, event.event_id, event.device_id,
        event.occurred_at, JSON.stringify(event))
      console.log(JSON.stringify({ event_id: event.event_id, queued: result.changes === 1 }))
      // Acknowledge only after the durable insert, including an existing duplicate.
      res.writeHead(204).end()
    } catch {
      console.error('Noise event could not be stored; leave delivery retryable.')
      res.writeHead(500).end()
    }
  }).listen(Number(process.env.PORT || 3000), '127.0.0.1')
  ```
</Accordion>

`SeamWebhook.verify` returns the event itself. Read `event.event_type`, not `req.body.event`. For a matching noise event, a `204` response means it is stored or was already stored. Other event types are acknowledged and ignored. Invalid signatures receive `400`; a failed database write receives `500` so delivery can retry.

The `(workspace_id, event_id)` primary key survives a process restart and prevents a repeated delivery from creating another pending alert. This does not make an external notification exactly-once. Use a worker with an idempotency key and a recorded delivery result. For a deployment with multiple receivers, we recommend one shared durable database so all instances use the same duplicate record.

Read `occurred_at` when ordering notices. They can arrive out of order. Retain provider metadata as context, but do not depend on an undocumented metadata field being present. Avoid escalating a delayed notice after a later resolved state without checking your application's current incident state.

### Verify the receiving path

In a sandbox workspace, use [Simulate Triggering a Noise Threshold](/docs/api/noise_sensors/simulate/trigger_noise_threshold), then check both webhook delivery and the stored alert. A simulator event in the event list alone does not prove your webhook received it.

Confirm all of these before deploying:

* One delivered event creates one pending row; replaying it still leaves one row.
* Restart the receiver and replay it again; the row count stays the same.
* An invalid signature creates no row. A storage failure returns a retryable response.
* Logs distinguish received, rejected, duplicated and pending events. Monitor webhook failures and worker backlog.

Sandbox checks establish the application and delivery path, not acoustic detection on physical hardware. Test the installed sensor separately. A webhook integration does not establish blanket privacy or legal compliance.

## Next Steps

* [Configure noise thresholds](/docs/capability-guides/noise-sensors/configure-noise-threshold-settings).
* [Connect and test Minut](/docs/device-and-system-integration-guides/minut-sensors/get-started-with-minut-sensors).
* [Read the noise event contract](/docs/api/noise_sensors/noise_thresholds/events).
* [Start in a Seam sandbox](https://console.seam.co/).
