sentinelHow it worksPricingBlogSign in

Start

IntroductionQuickstartAuthentication

Guides

Monitor a contractAdmin changesLarge outflowssBTC and PoX-5Slack and webhooks

Concepts

Watches & alertsBilling

Reference

CLIMCP serverSDKAPI reference
Powered bysecondlayer
Guides / Slack and webhooks
Previous← sBTC and PoX-5NextWatches & alerts →

On this page

Send Stacks contract alerts to Slack or a signed webhook

Route a watch's alerts to a Slack channel or to your own HTTP endpoint, signed so your handler can trust them. Two destinations, one --to on the watch's line.

The examples use example-vault; swap in your own contract.

Slack

Connect the Sentinel Slack app to your workspace once, then name a channel.

shell
sentinel connect slack
$ sentinel destinations add slack '#oncall' --default
$ sentinel when call example-vault set-paused --to slack:#oncall

connect slack prints a link that adds the app (it works once, for 10 minutes). After you allow the app, a page names your Sentinel account and asks you to confirm: nothing is connected until you press Connect. --default sends every watch that names no --to to this channel.

A private channel needs /invite @Sentinel, then its channel id (#C0123ABCD) instead of its name. An alert posted by the app keeps Mute 1 h and Mark read as buttons. Anyone in the channel can press them. A pasted Slack incoming-webhook URL works too: sentinel destinations add slack https://hooks.slack.com/services/… --label "#oncall".

A signed webhook

shell
sentinel destinations add webhook https://example.com/hook --label ops
$ sentinel when call example-vault set-paused --to webhook:ops

The signing secret is printed once, when you add the destination. Store it as SENTINEL_WEBHOOK_SECRET. Each destination has its own.

Over MCP, destinations_add takes the same arguments: destinations_add { kind: "webhook", target: "https://example.com/hook", label: "ops" }.

The body

Every webhook body is one envelope. type says what data is, and id is also the webhook-id header.

json
{
  "id": "evt_alr_…",
  "type": "alert.created",
  "version": 1,
  "createdAt": "2026-10-09T14:02:11.000Z",
  "test": false,
  "data": { "id": "alr_…", "alertLevel": "urgent", "headline": "…", "facts": [], "signals": [] }
}

data is the alert: its headline, typed facts, the signals that fired it, and the inspect commands. The full field list is in the reference.

Verify the signature

Each request carries webhook-id, webhook-timestamp and webhook-signature. verifyWebhook from @secondlayer/sentinel checks them, refuses a timestamp more than 5 minutes off, and returns the event. Pass the raw body, before any JSON parsing.

ts
import { verifyWebhook } from "@secondlayer/sentinel";

export async function POST(req: Request) {
  const event = await verifyWebhook(await req.text(), req.headers, process.env.SENTINEL_WEBHOOK_SECRET!);
  if (await seen(event.id)) return new Response(null, { status: 204 }); // a retry
  if (event.type === "alert.created" && event.data.alertLevel === "urgent") await page(event.data);
  return new Response(null, { status: 204 });
}

Without the package, the signature is the Standard Webhooks scheme: v1, then the base64 HMAC-SHA256 of webhook-id.webhook-timestamp.body. Details are in Verify the signature.

Check it before a real alert

shell
sentinel test-alert example-vault --yes

This sends the newest real match of the contract's watches from the last 90 days, marked as a test with its date. The webhook body carries test: true.

What to know

After the blockAn alert arrives after the block that holds the event, never from the mempool. The measured delay is public: sentinel status.
RetriesA failed delivery is retried with backoff, up to 5 attempts. A retry reuses the same webhook-id and is signed again with a fresh timestamp, so dedupe on webhook-id only.
Failed endpointIf every attempt fails, you get an email and your other webhooks get delivery.failed. Fix the endpoint, then run sentinel redeliver <alert>.
Slack buttonsAnyone in the channel can press Mute and Mark read.
CostAlerts are never charged. Live mainnet alerts start at Builder, $19 per contract a month (Pricing).

Related

  • Watches & alerts: where alerts go
  • Monitor a Stacks contract and get alerts
  • sBTC and PoX-5 alerts