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

# On-site 911 notifications: configure Kari's Law safety alerts

> Turn on the per-organization notification that alerts your safety contacts — by email, an HMAC-signed HTTPS webhook, and Slack — the moment a 911 dial is detected on any calling surface.

# On-site 911 notifications (Kari's Law)

Kari's Law (47 U.S.C. § 623; FCC rules at 47 CFR § 9.16) places two
duties on a multi-line telephone system in the US: 911 must be
reachable without a prefix, and the instant a user dials it, a
designated on-site point — a front desk, a security office, a safety
manager — must be notified so someone can meet responders and direct
them to the caller.

If you run Orbit as the phone system for a US site, the on-site
notification is a control you configure. You choose the safety
contacts and the endpoints; Orbit provides the fan-out. This guide
covers turning it on, what each channel sends, how to rehearse it
end-to-end, and what a failing target means.

The other half of the statute, and what Orbit's blocked emergency
dialing means for your disclosure obligations, is covered in
[Emergency Calling: Kari's Law and RAY BAUM'S Act posture](/compliance/emergency-calling-and-karis-ray-baums-posture).

## What Orbit implements

The notification is per-organization and off by default. Until you
enable it and configure at least one channel, nothing changes for
your deployment.

Once enabled, the moment a dial on any outbound surface resolves to a
blocked emergency code — 911, 112, 999, or 000 — Orbit fans the alert
out to every channel you configured:

* **Email** to each safety contact (up to 25).
* **Webhook**, an HTTPS POST to your security-desk or SIEM endpoint,
  optionally HMAC-signed.
* **Slack**, an Incoming Webhook post to a channel you choose.

The alert fires on every outbound dial surface: a direct dial, a
blind transfer, a warm-transfer consult leg, and adding a conference
participant. Firing is best-effort and never changes how the dial
itself is handled (see "Failure semantics" below).

## Before you enable: the notification is advisory

Orbit does not route emergency calls. Live emergency codes are
rejected at every dispatch layer whether or not this notification is
configured — that block is shipped platform behavior, described in
[Emergency calling](/voice/emergency-calling). The notification does
not create an emergency path; it tells your safety desk that a user
just tried to reach one, so staff can check on the caller and use the
real path.

Before enabling, make sure your emergency plan already covers:

* A working fallback at every site — a landline or mobile that
  reaches 911 outside Orbit.
* Signage and onboarding disclosure that Orbit phones cannot place
  emergency calls.
* A staffed point to receive the alerts you are about to configure.
  An alert nobody reads is worse than none, because it looks like
  coverage.

The posture page carries the full mitigation list:
[Emergency Calling posture](/compliance/emergency-calling-and-karis-ray-baums-posture).

## Configure the fan-out

Two admin endpoints manage the configuration (organization owner or
admin with voice write access):

* `GET /api/v1/voice/emergency-notification`
* `PUT /api/v1/voice/emergency-notification`

The configuration is stored on your organization under the voice
settings key `emergency_notifications`; the PUT replaces only that
block and preserves every other voice and organization setting.

### Read the current configuration

```bash cURL theme={null}
curl https://api.orbit.devotel.io/api/v1/voice/emergency-notification \
  -H "X-API-Key: $ORBIT_API_KEY"
```

```json theme={null}
{
  "data": {
    "enabled": true,
    "safety_contacts": [
      { "name": "Front desk", "email": "security@example.com" },
      { "name": "Safety manager", "email": "safety@example.com" }
    ],
    "webhook_url": "https://siem.example.com/hooks/orbit-emergency",
    "webhook_secret_set": true,
    "slack_webhook_url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
  }
}
```

GET always returns `200` with the normalized record — never `404` —
so a rollout script can render the empty state directly.

### Enable or update

```bash cURL theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/voice/emergency-notification \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "safety_contacts": [
      { "name": "Front desk", "email": "security@example.com" },
      { "name": "Safety manager", "email": "safety@example.com" }
    ],
    "webhook_url": "https://siem.example.com/hooks/orbit-emergency",
    "webhook_secret": "your-shared-secret",
    "slack_webhook_url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
  }'
```

| Field | Rules |
| - | - |
| `enabled` | Must be `true` for alerts to fire. Enabling with zero configured channels is rejected with a `400` — an enabled configuration that can reach nobody is a silent-failure trap. |
| `safety_contacts` | Up to 25 entries of `{ email, name? }`. Each address receives one message per detected dial. |
| `webhook_url` | An `https://` URL. Validated before each delivery; addresses that resolve to internal or private network ranges are refused. |
| `webhook_secret` | Write-only. When set, webhook POSTs carry an HMAC signature (below). GET reports only `webhook_secret_set`. Omit the field to keep the stored secret; send explicit `null` to clear it. |
| `slack_webhook_url` | An `https://` Slack Incoming Webhook URL for the channel that should receive alerts. The same delivery validation as `webhook_url` applies. |

Every change is recorded in your audit log.

## Verify end-to-end

1. **Point each channel at an observable test target** — a test
   mailbox, a request catcher for the webhook, a private Slack
   channel.
2. **From a test extension, dial 911** on each surface you ship: the
   browser softphone, a registered desk phone, and a transfer flow.
   The dial is rejected with `EMERGENCY_CALLING_NOT_SUPPORTED` —
   expected; that reject is precisely what triggers the fan-out.
   Confirm the email, the webhook POST, and the Slack message all
   arrive, and that the dispatch's per-channel outcome landed in
   your audit log.
3. **Repeat with the other blocked codes** your users are likely to
   attempt: `112` on EU-issued devices, `999`, `000`.
4. **Rehearse the call path separately with the 933 drill**
   ([E911 emergency drill mode](/guides/e911-emergency-drill)). The
   drill rewrites 933 to a safe destination so agents can practise
   the "cannot reach 911, use the fallback line" flow. Because the
   rewrite never produces a blocked emergency dial, it does not
   trigger the on-site notification — the blocked-code dials in
   steps 2–3 are the notification rehearsal.

Dialing 911 in a test is safe by construction: blocked codes never
reach a carrier, so the rehearsal cannot touch a live PSAP.

## Payload reference

All three channels carry the same facts: the matched emergency code,
the exact value dialed, the surface the dial came from, the caller's
line and extension where known, a call reference, and the detection
timestamp.

### Email

Subject: `[Emergency] 911 dialed on your phone system`, with the code
reflecting whichever emergency code matched. The body lists each
fact on its own line, for example:

```text theme={null}
Emergency number dialed: 911
Dialed value: 911
Origin: outbound call
Caller line: +14155550142
Extension: 2001
Call reference: call_9d21c7aa
Detected at: 2026-10-14T18:42:05.193Z
```

Lines for facts the surface does not know — a caller line on a
softphone dial, for instance — are omitted.

### Security-desk webhook

A JSON POST with `Content-Type: application/json`:

```json theme={null}
{
  "event": "voice.emergency_call.detected",
  "emergency_code": "911",
  "dialed_value": "911",
  "origin": "outbound_call",
  "caller_number": "+14155550142",
  "initiated_by_user_id": "user_2f8Kd1Qm",
  "initiated_by_extension": "2001",
  "call_id": "call_9d21c7aa",
  "org_id": "org_51Jq0xTz",
  "tenant_schema": "tenant_51jq0xtz",
  "detected_at": "2026-10-14T18:42:05.193Z"
}
```

`origin` is one of `outbound_call` (a direct dial),
`blind_transfer`, `warm_transfer` (the consult leg), or
`conference_add`. Facts the surface does not know arrive as `null`.

Headers:

| Header | Value |
| - | - |
| `X-Devotel-Event` | `voice.emergency_call.detected` |
| `User-Agent` | `Devotel-Orbit-Emergency/1.0` |
| `X-Devotel-Signature` | Present only when `webhook_secret` is set: `t=<unix seconds>,v1=<hex>`, the HMAC-SHA256 of `"<t>.<raw body>"` under your secret. |

A 2xx response counts as delivered; anything else is recorded as a
failed delivery. Verify the signature before trusting a request:

```javascript theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyEmergencyWebhook(rawBody, header, secret) {
  const m = /^t=(\d+),v1=([0-9a-f]+)$/.exec(header ?? "");
  if (!m) return false;
  const expected = createHmac("sha256", secret)
    .update(`${m[1]}.${rawBody}`)
    .digest("hex");
  return timingSafeEqual(
    Buffer.from(m[2], "hex"),
    Buffer.from(expected, "hex"),
  );
}
```

### Slack

A standard Incoming Webhook POST: a one-line `text` summary plus a
bulleted rendering of the same facts as the email. Post it to a
channel that is staffed — or paged to on-call — not one that mutes by
default.

## Failure semantics: best-effort and non-blocking

The notification is advisory and independent of call handling:

* The emergency dial is rejected exactly the same way whether the
  fan-out succeeds, partially succeeds, or fails entirely. A
  notification failure can never delay, alter, or un-block a dial.
* Channels fail independently: a dead webhook does not stop the
  emails or the Slack post.
* Email uses the same delivery path and retries Orbit applies to
  other system email; webhook and Slack make a single attempt per
  event with a 10-second timeout.
* Targets that resolve to internal or private network addresses are
  refused at delivery time and recorded as failed.

Orbit does not retry a failed webhook on your behalf and cannot see
inside your SIEM, so monitoring the health of your endpoints is part
of the control you own. After every drill, check the audit entry for
the dispatch: it records which channels were configured and whether
each delivered, so a quietly dying endpoint shows up there before a
real event exposes it.

## Pair with your RAY BAUM'S Act location records

The on-site notification answers "who was told"; a dispatchable
location answers "where is the caller." Register per-number
emergency addresses and keep them current — floor, suite, room — so
the safety desk that receives an alert can direct responders to the
right place: [Emergency address](/numbers/emergency-address). The
two controls are independent and both tenant-owned; the posture page
maps them to the statutes:
[Emergency Calling posture](/compliance/emergency-calling-and-karis-ray-baums-posture).

## Related references

* [Emergency calling](/voice/emergency-calling) — the platform block
  on 911 / 112 / 999 / 000 and the error shape your integration
  sees.
* [Emergency Calling: Kari's Law and RAY BAUM'S Act posture](/compliance/emergency-calling-and-karis-ray-baums-posture) —
  the statutes, who they bind, and the tenant-owned mitigation list.
* [E911 emergency drill mode](/guides/e911-emergency-drill) — rehearse
  dial behavior with 933 without touching a live PSAP.
* [Emergency address](/numbers/emergency-address) — the
  dispatchable-location record that pairs with this notification.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.