Skip to content
Sendora Cloud
Create account
Operate

Ops Alerts

Ops Alerts — route platform events to Slack or a webhook. Per-project channels, rule matching on event type (with a cooldown throttle), delivery logs and retries.

Features

  • **Rule-based routing** — match on event type + properties + module; multiple matchers per rule with AND/OR composition.
  • **Slack channels** — incoming webhook URL per channel; payload formatted for Slack's `attachments` shape with module-tinted colour bars.
  • **Generic HTTPS webhooks** — for ops platforms that aren't Slack (Discord, MS Teams via custom adapter, internal incident systems).
  • **Retry worker** — transient 5xx + network errors retried with exponential backoff; `notifications_logs` row tracks every attempt with status code + error body.
  • **Per-channel dispatch log** — every alert routed is recorded so on-call can audit "did we get pinged for X" without grepping Slack.
  • **Test channel button** — verifies your Slack webhook URL is live before you trust a 3am page to it.
  • **Same event source as Automation + Webhooks** — your platform event taxonomy fires alerts, runs workflows, AND notifies subscribed webhook endpoints out of one stream. No second event pipeline.

Common use cases

  • Replace PagerDuty alert routing for ops events you already track in Sendora (delivery failures, anomaly detections, signin storms).
  • Slack channel for every module — `#sendora-email` for bounces, `#sendora-auth` for signin alerts, `#sendora-ops` for everything else.
  • Custom incident integration — POST event payloads to your in-house on-call system from the same rule engine that drives Slack pings.

Key concepts

Type match vs. volume threshold
A notification rule fires on the PRESENCE of a matching event type, throttled by cooldownMinutes — it does not count. For 'alert when more than N events of a type in M minutes' or 'a type stopped arriving' (a dead-man's switch), create an Observability alert rule on the event_volume metric with an eventTypes filter (metric + operator gt/lt + threshold + windowMinutes + eventTypes). Without eventTypes, event_volume counts every type in scope — it answers 'did anything arrive', not 'did more than N of THIS type' — so set eventTypes to scope it. It evaluates on a timer and delivers through these same channels.

Notifications

Create a Slack (or webhook) channel

FREE

A channel is a destination. `type` is `slack` (an incoming-webhook URL) or `webhook` (any HTTPS endpoint, HMAC-signed). The URL is a bearer credential — anyone holding it can post to the channel — so it is encrypted at rest and only ever returned masked to its last 6 characters. Create it under a project to scope it there; the org-level path applies to every project.

# Slack — per project
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_ID>/projects/<PROJECT_ID>/notifications/channels \
  -H "x-api-key: sk_prod_…" \
  -d '{
    "name": "Prod alerts",
    "type": "slack",
    "config": { "webhookUrl": "https://hooks.slack.com/services/T000/B000/xxxx" }
  }'

# Verify it before relying on it — posts a real message to the channel
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_ID>/projects/<PROJECT_ID>/notifications/channels/{{CHANNEL_ID}}/test \
  -H "x-api-key: sk_prod_…"

Route events to a channel

FREE

A rule matches event types and fans out to one or more channels, evaluated on event ingest. A project-scoped rule fires only for that project's events and may only reference that project's channels; an org-wide rule fires for everything. Channels and rules are separate so one destination can serve many rules, one rule can hit several destinations, and the credential is stored (and rotated) in exactly one place. A rule here fires on the PRESENCE of a matching event type, throttled by cooldownMinutes — it does not count events. To alert on a VOLUME — "more than N events of a type in M minutes" (operator gt) or "a type stopped arriving" (lt 1, a dead-man's switch) — create an Observability alert rule on the event_volume metric with an eventTypes filter (metric + operator + threshold + windowMinutes + eventTypes). ⚠ Without eventTypes, event_volume counts EVERY event type in scope, so it answers "did anything arrive", not "did more than N of THIS type" — set eventTypes to scope it. It evaluates on a timer and delivers through these same channels.

curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_ID>/projects/<PROJECT_ID>/notifications/rules \
  -H "x-api-key: sk_prod_…" \
  -d '{
    "name": "Account takeovers",
    "eventTypes": ["auth.device_takeover", "auth.user_deleted"],
    "channelIds": ["{{CHANNEL_ID}}"]
  }'

Send a custom Slack message from your server

FREE

Two steps, and the second one is the message. (1) Create a rule whose `messageTemplate` is the sentence you want in Slack. (2) From your server, POST the event it listens for -- the properties you send ARE what the template renders. There is no separate send-to-Slack call: any event your backend already emits can become an alert. Without a template an alert posts the event type and a JSON dump of its properties -- accurate, and not something you want woken up by. Set `messageTemplate` on the rule to send a sentence instead. `{{event.type}}`, `{{properties.<key>}}` (nested paths work) and `{{rule.name}}` resolve against the event; a placeholder that does not resolve renders empty rather than leaving braces in the message. Substituted values are escaped for Slack, so an event property containing `<!channel>` posts as text instead of notifying your workspace -- your own template text is left alone, so `*bold*` and real links still work. Leave it unset to keep the default layout.

curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_ID>/projects/<PROJECT_ID>/notifications/rules \
  -H "x-api-key: sk_prod_…" \
  -d '{
    "name": "Payout failures",
    "eventTypes": ["ops.payout_failed"],
    "channelIds": ["{{CHANNEL_ID}}"],
    "messageTemplate": ":rotating_light: *{{properties.merchant}}* payout failed \u2014 {{properties.amount}} ({{properties.reason}})"
  }'

# Emit the event your template reads from -- the properties ARE the message
curl -X POST https://api.sendoracloud.com/api/v1/events \
  -H "x-api-key: pk_prod_…" \
  -d '{
    "eventType": "ops.payout_failed",
    "properties": { "merchant": "TapMap", "amount": "\u00a3240.00", "reason": "card_declined" }
  }'

Read delivery logs

FREE

Every attempt is recorded per rule x channel with the response code, so a failing endpoint is visible without waiting for someone to notice missing alerts. Failed deliveries are retried.

curl "https://api.sendoracloud.com/api/v1/orgs/<ORG_ID>/projects/<PROJECT_ID>/notifications/logs" \
  -H "x-api-key: sk_prod_…"

# Aggregate delivery health
curl https://api.sendoracloud.com/api/v1/orgs/<ORG_ID>/projects/<PROJECT_ID>/notifications/logs/stats \
  -H "x-api-key: sk_prod_…"

Related