POST
/api/alerts/trigger/{webhookUrl}{webhookUrl} is the webhook's URL identifier — find it on the webhook detail page in the dashboard.
Headers
| Field | Type | Description |
|---|---|---|
X-Webhook-Signaturerequired | string | HMAC-SHA256 of the request body, hex-encoded, signed with the team webhook secret. See Authentication. |
Content-Typerequired | application/json | Always required. |
Body
| Field | Type | Description |
|---|---|---|
titlerequired | string | Alert title, max 200 chars. Shown in Slack and notifications. |
description | string | Long-form context, max 2000 chars. |
severity | "sev0" | "sev1" | "sev2" | "sev3" | "sev4" | Overrides the webhook default. Drives notification policy routing. |
metadata | object | Arbitrary key-value payload, max 100 KB, max 10 nesting levels. Available to filter rules. |
group_key | string | Explicit grouping key, max 200 chars. Identical keys fold into one open alert. |
Example request
Build the JSON body, sign it with HMAC-SHA256 using your team webhook secret, and POST the signed bytes.
BODY='{"title":"Deploy failed: api-gateway","description":"Migration step exited with code 137","severity":"sev1","metadata":{"commit":"a1b2c3","environment":"production"},"group_key":"deploy:api-gateway"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$OPSHIFT_SECRET" -hex | awk '{print $2}')
curl -X POST https://www.opshift.io/api/alerts/trigger/deploy-failures \
-H "Content-Type: application/json" \
-H "X-Webhook-Signature: $SIG" \
-d "$BODY"import { createHmac } from "node:crypto";
const secret = process.env.OPSHIFT_SECRET;
const body = JSON.stringify({
title: "Deploy failed: api-gateway",
description: "Migration step exited with code 137",
severity: "sev1",
metadata: { commit: "a1b2c3", environment: "production" },
group_key: "deploy:api-gateway",
});
const signature = createHmac("sha256", secret).update(body).digest("hex");
const res = await fetch(
"https://www.opshift.io/api/alerts/trigger/deploy-failures",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Webhook-Signature": signature,
},
body,
},
);
console.log(await res.json());import hashlib
import hmac
import json
import os
import urllib.request
secret = os.environ["OPSHIFT_SECRET"]
body = json.dumps({
"title": "Deploy failed: api-gateway",
"description": "Migration step exited with code 137",
"severity": "sev1",
"metadata": {"commit": "a1b2c3", "environment": "production"},
"group_key": "deploy:api-gateway",
}).encode()
signature = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
request = urllib.request.Request(
"https://www.opshift.io/api/alerts/trigger/deploy-failures",
data=body,
method="POST",
headers={
"Content-Type": "application/json",
"X-Webhook-Signature": signature,
},
)
with urllib.request.urlopen(request) as response:
print(response.read().decode())package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
)
func main() {
secret := os.Getenv("OPSHIFT_SECRET")
body := []byte(`{"title":"Deploy failed: api-gateway","severity":"sev1","group_key":"deploy:api-gateway"}`)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
signature := hex.EncodeToString(mac.Sum(nil))
req, _ := http.NewRequest(
http.MethodPost,
"https://www.opshift.io/api/alerts/trigger/deploy-failures",
bytes.NewReader(body),
)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Webhook-Signature", signature)
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}use hmac::{Hmac, Mac};
use sha2::Sha256;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let secret = std::env::var("OPSHIFT_SECRET")?;
let body = r#"{"title":"Deploy failed: api-gateway","severity":"sev1","group_key":"deploy:api-gateway"}"#;
let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())?;
mac.update(body.as_bytes());
let signature = hex::encode(mac.finalize().into_bytes());
let response = reqwest::Client::new()
.post("https://www.opshift.io/api/alerts/trigger/deploy-failures")
.header("Content-Type", "application/json")
.header("X-Webhook-Signature", signature)
.body(body)
.send()
.await?;
println!("{}", response.text().await?);
Ok(())
}Response
Returns 200 OK. The status field tells you what happened:
json
{
"alertId": "65f3a2...",
"status": "created",
"message": "Alert created",
"occurrenceCount": 1
}json
{
"alertId": "65f3a2...",
"status": "occurrence_added",
"message": "Occurrence added to open alert",
"occurrenceCount": 7
}json
{
"alertId": "65f3a2...",
"status": "reopened",
"message": "Alert reopened",
"occurrenceCount": 8
}json
{
"status": "filtered",
"message": "Alert suppressed by filter rule",
"reason": "metadata.branch != main"
}Errors
| Field | Type | Description |
|---|---|---|
400 | Bad request | Missing required field, invalid severity, or metadata exceeded size/nesting limits. |
401 | Unauthorized | Missing or invalid signature. |
403 | Forbidden | Team subscription expired. |
404 | Not found | Unknown webhook URL or webhook is disabled. |
429 | Too many requests | Per-webhook rate limit or monthly alert quota exhausted. |
Use group_key for repeated incidents
A stable group key — e.g. db:write-timeout:checkout-svc — means the
hundredth failure during an incident folds into the open alert instead of
paging again. Reserve unique keys for genuinely distinct incidents.