Sentinel API v1

从第一次 API 调用到生产部署

Webhooks

Reliable, verifiable event delivery

A Webhook endpoint is a durable resource. Events are committed locally before the outbox delivers them asynchronously, so a slow or unavailable receiver never blocks camera capture or inference.

A signed Webhook is the cross-system output for alerts and errors, backed by the persistent Event lifecycle. Ordinary Monitor inference text is not written one-by-one to the Event ledger and is not a substitute for /v1/analyses or /v1/analyses/stream.

Create an endpoint

curl -X POST http://localhost:8000/v1/webhook-endpoints \
  -H "Authorization: Bearer $SENTINEL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: soc-webhook-1" \
  -d '{
    "name": "SOC intake",
    "url": "https://soc.example.com/sentinel/events",
    "secret_env": "SENTINEL_WEBHOOK_SIGNING_SECRET",
    "enabled": true,
    "dry_run": false,
    "filters": {
      "event_types": ["monitor.alert.raised", "source.disconnected"],
      "source_ids": ["src_01..."],
      "monitor_ids": ["mon_01..."]
    }
  }'

secret_env names a runtime environment variable, not the secret itself. An enabled endpoint requires a non-placeholder secret of at least 32 bytes at enrollment, readiness, and every delivery. Responses expose availability only and never return it.

Public filters support severities (safe, suspicious, critical), event_types, states (raised, updated, cleared), plus source_ids and monitor_ids. Each resource-ID filter accepts up to 100 IDs; values are allow-lists within that filter, while an empty array means no restriction.

Treat the full receiver URL as sensitive: some SaaS paths/queries are bearer capabilities. Endpoint URLs are currently stored in local SQLite and included in volume backups. Prefer a token-free receiver URL plus independent HMAC, and keep databases/backups encrypted and tightly access-controlled.

Outbound target safety

Public endpoints require HTTPS by default. The runtime rejects embedded credentials, metadata/loopback/private/link-local targets, mixed DNS answers, redirects, and environment proxies. At connect time it validates the complete DNS set, dials only a validated numeric IP, and preserves the original hostname for TLS SNI, certificate verification, and Host. An intentional private receiver requires an exact host or host:port in SENTINEL_WEBHOOK_ALLOWED_HOSTS; wildcards are not accepted.

Test through the real outbox

POST /v1/webhook-endpoints/{endpoint_id}/test
Idempotency-Key: webhook-test-20260829-1

{
  "severity": "suspicious",
  "title": "Sentinel webhook test",
  "summary": "Verify receipt and signature before enabling production events."
}

A test uses the same persistence, signature, and retry path as a real Event; it is not a one-off direct HTTP request.

Sign the exact raw body

X-Sentinel-Delivery-ID: whd_...
X-Sentinel-Event-ID: evt_...
X-Sentinel-Timestamp: 1788056468
X-Sentinel-Signature: v1=<hex hmac sha256>
Idempotency-Key: whd_...

signed_bytes = utf8(timestamp) + b"." + raw_request_body
import crypto from "node:crypto";

export function verifySentinel(secret, timestamp, rawBody, signature) {
  const expected = "v1=" + crypto
    .createHmac("sha256", secret)
    .update(timestamp + ".")
    .update(rawBody)
    .digest("hex");
  const left = Buffer.from(signature);
  const right = Buffer.from(expected);
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}

The receiver must preserve raw bytes, check timestamp freshness and the v1 version, compare the signature in constant time, and only then parse JSON. After parsing, require schema=sentinel.event.v1, an evt_* ID, and type/state/severity; X-Sentinel-Event-ID must equal the body ID, while Idempotency-Key must equal X-Sentinel-Delivery-ID.

At-least-once delivery

Receivers must deduplicate by Event ID plus Delivery ID and return 2xx quickly after durable acceptance. Transient failures use bounded exponential backoff with jitter. Permanent 4xx responses are not retried except 408, 409, 425, and 429.

GET  /v1/webhook-deliveries?status=dead_letter
GET  /v1/webhook-deliveries/{delivery_id}
POST /v1/webhook-deliveries/{delivery_id}/retry

Image evidence is a URL

Normal webhook Events carry a protected evidence.image_url. The receiver downloads binary JPEG using a Sentinel API key with events:read under the same authorization check as Event JSON. Inline Base64 is a bounded compatibility option, not the default.

Camera/model summary and analysis are untrusted data. A downstream AI agent must never treat scene text or model prose as instructions.

Compatibility surface: existing realtime SDK

Existing administrator clients can continue using the durable SSE and acknowledgement API. These /api/sdk paths are compatibility interfaces, not part of the v1 public contract; new integrations should use v1 Events, Webhooks, and image URLs.

GET  /api/sdk/capabilities
GET  /api/sdk/alerts
GET  /api/sdk/alerts/stream
POST /api/sdk/alerts/{id}/acknowledge
POST /api/sdk/alerts/test
import { SentinelAlerts } from "./sentinel-alerts.js";

const client = new SentinelAlerts({ baseUrl, token });
client.onAlert(async (event) => {
  if (event.suggested_actions.includes("play_sound")) {
    await SentinelAlerts.playSound("/alarm.mp3");
  }
  await siteAutomation.handle(event);
  await client.acknowledge(event.id, { action: "dispatched" });
});
client.connect();
The compatibility SDK provides typed, authenticated events and acknowledgements. Arbitrary equipment operations stay in the customer's trusted client so the monitoring service never executes third-party code.