Skip to content

Webhook

The generic webhook channel is the fallback. When you need to push alerts to a chat surface, ticketing system, or in-house relay that Ongrid doesn't speak natively (Microsoft Teams, Mattermost, Rocket Chat, OpsGenie, PagerDuty, your own router), point a generic webhook at it and the alert pipeline posts the canonical Ongrid message JSON.

Use the dedicated channel when there is one

For Slack / Feishu / DingTalk / WeCom / Telegram, the dedicated channels render richer payloads (Slack attachments, signing). Reach for the generic webhook only when there isn't a dedicated channel.

Payload

The body is the canonical notify.Message shape, JSON-encoded verbatim:

json
{
  "severity": "critical",
  "subject": "node-01 swap_high",
  "body": "swap_in_pages > 1000 for 5m",
  "source": "alert",
  "labels": {
    "rule": "swap_high",
    "incident_id": "1234",
    "device_id": "7",
    "host": "node-01"
  },
  "dedupe_key": "alert:swap_high:device=7",
  "occurred_at": "2026-05-30T14:02:35Z"
}

No envelope, no msgtype, no flattening. The receiver gets exactly the fields the alert pipeline produced. Build by NewGenericWebhookSender.

Optional HMAC signing

When you fill the Secret field, every request carries an X-Ongrid-Signature header:

text
X-Ongrid-Signature: sha256=<hex>

Where <hex> is hex(HMAC-SHA256(secret, body)) over the exact bytes posted. The signer is signGenericWebhook.

Verifying on the receiver side

python
# Python receiver
import hmac, hashlib

def verify(secret, body_bytes, header):
    expected = "sha256=" + hmac.new(
        secret.encode(), body_bytes, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)
go
// Go receiver
func verify(secret string, body, header []byte) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := []byte("sha256=" + hex.EncodeToString(mac.Sum(nil)))
    return hmac.Equal(expected, header)
}

Always verify the signature when the endpoint is public

A generic webhook URL with no signature is open to anyone who learns the URL. The signature header is the only thing that authenticates "this is from Ongrid".

Request shape

PropertyValue
MethodPOST
Content-Typeapplication/json
User-Agentongrid-notify/1.0
BodyJSON of notify.Message
Sig headerX-Ongrid-Signature: sha256=… (only when secret)
TimeoutPer-request http.Client timeout (default 15s)

Success criterion

The Sender treats HTTP 2xx (200–299) as success. Anything else — including 3xx redirects, 4xx, 5xx — is unexpected status: … and counts as a failed delivery for the dampening pipeline. The body of the response is not parsed; Ongrid does not care what JSON your relay echoes back.

Retry and dampening

Retry semantics are owned by the alert pipeline, not the Sender. The Sender posts exactly once per Send(ctx, msg) call. The pipeline decides:

  • Dampening window. Per-rule, default 5 minutes. The same dedupe key fires at most once per window.
  • Repeat-on-still-firing. Default 1 hour. If an incident is still open after this window, the channel gets a re-notify so it isn't lost in chat history.
  • Recovery message. Fired when the alert clears.

The Sender's job is to push the bytes. The pipeline's job is to decide when.

No automatic retry on transport failure

If the receiver is down at the moment the Sender posts, the message is lost (logged as a delivery error, surfaced in the per-channel delivery stats). The pipeline does not queue and re-attempt later. The reasoning: a 5-minute-stale alert delivered three minutes after the recovery is more confusing than a missing one. Use a robust receiver, or layer your own queue between Ongrid and a flaky downstream.

Setup

  1. Stand up an HTTP endpoint that accepts POST + application/json. Pick a strong secret you'll share with Ongrid.
  2. In Ongrid: Settings → Channels → New → Provider = webhook → Endpoint = your URL → Secret = the shared secret.
  3. Click Test. The test message is the synthetic notify.Message{Severity: "info", Subject: "Ongrid test", …}. Verify the signature header in your receiver logs.

Recipes

Microsoft Teams (incoming webhook)

Teams's incoming webhook accepts a different payload shape (a MessageCard). The simplest path is to put a tiny adapter between Ongrid and Teams:

python
# server.py - listens on /ongrid-to-teams
@app.post("/ongrid-to-teams")
def relay(msg: dict):
    teams_url = os.environ["TEAMS_WEBHOOK"]
    card = {
        "@type": "MessageCard",
        "@context": "https://schema.org/extensions",
        "themeColor": {"critical":"D92F2F","warning":"F2C037","info":"36A64F"}.get(msg["severity"], "6F7A87"),
        "title": msg["subject"],
        "text": msg["body"],
    }
    requests.post(teams_url, json=card)

Point the Ongrid webhook channel at https://your-relay/ongrid-to-teams.

PagerDuty Events API v2

Same pattern: a small relay that translates {severity, subject, body, dedupe_key} into a PagerDuty events.v2 payload. The PD dedup_key field maps 1:1 to Ongrid's dedupe_key, so the same incident in Ongrid is the same alert in PD.

Splunk HEC / Elastic ingest

Point the channel at the HEC endpoint with the Splunk token in the URL. Splunk accepts the canonical Ongrid JSON; index on severity and labels.rule for downstream search.

Field reference

See Message for the source of truth. Stable fields:

FieldTypeNotes
severitystringcritical / warning / info.
subjectstringRule name + target — one line.
bodystringMulti-line detail. May contain newlines.
sourcestringalert / test / etc.
labelsmap[string]stringrule, incident_id, device_id, custom.
dedupe_keystringpipeline:rule:label-set. Stable per identity.
occurred_atRFC3339 stringUTC. The moment the alert evaluator fired.

New fields are additive — receivers should ignore unknown keys.