EvoHub Docs Sign in

Integrations

Alert API and generic webhook

Use with AI
View as MarkdownThis page as plain text, for pasting into an AI tool Open in ClaudeAsk Claude questions about this page Open in ChatGPTAsk ChatGPT questions about this page
Connect to Cursor / VS Code / ClaudeSearch and read these docs from your AI tool (MCP server)

MCP server URL

https://docs-dev.evohub.io/mcp

Claude Code

claude mcp add --transport http evohub-docs-docs https://docs-dev.evohub.io/mcp

Claude (claude.ai and Claude Desktop): Settings → Connectors → Add custom connector, and paste the URL above.

Claude Desktop — claude_desktop_config.json

{
  "mcpServers": {
    "evohub-docs-docs": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://docs-dev.evohub.io/mcp"
      ]
    }
  }
}

Cursor — ~/.cursor/mcp.json

{
  "mcpServers": {
    "evohub-docs-docs": {
      "url": "https://docs-dev.evohub.io/mcp"
    }
  }
}

VS Code — .vscode/mcp.json

{
  "servers": {
    "evohub-docs-docs": {
      "type": "http",
      "url": "https://docs-dev.evohub.io/mcp"
    }
  }
}

If your tool is not in the catalog, or you want to raise alerts from your own code, use the API integration. It accepts a small JSON body in which only a summary is required, and it can also acknowledge and resolve alerts. Its body is compatible with the Events v2 shape used by other paging tools, so a tool that already speaks it only needs a new URL and key.

Set up the API integration

Create the integration

Go to On-Call → Integrations → + Add Integration, choose API, choose an Escalation Policy and click Create Integration.

Copy the URL and key

Open the integration. The Webhook URL is https://evohub.io/ingest/api?key=<key>; the API Key is the key on its own.

Send a test event

Run the curl example below with your key and check that an alert appears in On-Call → Alerts.

Send an event

POST https://evohub.io/ingest/api with a JSON body. Send the integration key in whichever way your tool makes easiest:

  • "routing_key" in the body,
  • ?key= in the URL, or
  • an Authorization: Bearer <key> header.

Minimal trigger

curl -X POST 'https://evohub.io/ingest/api' \
  -H 'Authorization: Bearer YOUR_INTEGRATION_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"summary": "Database primary is down"}'

Full trigger

curl -X POST 'https://evohub.io/ingest/api' \
  -H 'Content-Type: application/json' \
  -d '{
    "routing_key": "YOUR_INTEGRATION_KEY",
    "event_action": "trigger",
    "dedup_key": "db-primary-down",
    "payload": {
      "summary": "Database primary is down",
      "source": "db-01.prod.acme.example",
      "severity": "critical",
      "component": "postgres",
      "group": "payments",
      "custom_details": { "region": "eu-central" }
    }
  }'

Resolve

curl -X POST 'https://evohub.io/ingest/api' \
  -H 'Content-Type: application/json' \
  -d '{
    "routing_key": "YOUR_INTEGRATION_KEY",
    "event_action": "resolve",
    "dedup_key": "db-primary-down"
  }'

Request fields

You can send the alert details nested in payload (Events v2 style) or as flat fields at the top level. A flat field fills the matching payload field when that one is empty.

Field Required Description
routing_key No* The integration key. *Required unless sent as ?key= or a bearer token.
event_action No trigger (default), acknowledge or resolve.
dedup_key For acknowledge and resolve Identifies the alert. On a trigger without one, EvoHub derives a key from the summary and source and returns it.
payload.summary / summary / title For trigger The alert's title.
payload.severity / severity No critical, high, medium, low or info. The Events v2 values are accepted too: error is read as high and warning as medium. Anything else, or nothing, is medium.
payload.source / source No Where the problem is, for example a host name. Stored as the source label.
description / payload.description No The alert's description.
payload.component, payload.group, payload.class No Stored as labels of the same name.
payload.custom_details No An object; each entry becomes a label, its value as text.
payload.timestamp, client, client_url No Accepted for compatibility and ignored. The alert is stamped when it arrives.

Response

A processed event returns 202:

{
  "data": {
    "status": "success",
    "message": "Event processed",
    "dedup_key": "db-primary-down"
  },
  "success": true
}

Keep the dedup_key if you let EvoHub derive it — you need it to resolve the alert later.

A request that was not processed returns the same envelope with "status": "invalid event" and a message:

Status When Example message
400 The body is not JSON, a trigger has no summary, an acknowledge or resolve has no dedup_key, or event_action is unknown. summary is required
401 No key was sent, or it names no enabled integration. invalid routing_key
500 The alert could not be created, acknowledged or resolved. failed to create alert
503 The key could not be checked right now. Retry after the Retry-After seconds. could not verify routing_key right now; retry later

How events behave

  • Trigger opens an alert and starts the integration's escalation policy. A trigger whose dedup_key matches an alert that is still open does not open a second one; it is recorded as Retriggered on the existing alert.
  • Acknowledge acknowledges the open, triggered alert with that dedup_key.
  • Resolve resolves the open alert with that dedup_key.
  • Acknowledging or resolving a dedup_key that has no open alert does nothing and still returns 202.
  • The integration's escalation policy always applies; the request cannot choose another one.

Generic alert webhook

EvoHub also accepts a simpler, flat alert format at /ingest/alerts. Create a Webhook integration for it (the key of an API integration works too):

Trigger

curl -X POST 'https://evohub.io/ingest/alerts?key=YOUR_INTEGRATION_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Disk almost full on web-03",
    "description": "/var is at 93%",
    "severity": "high",
    "fingerprint": "web-03-disk-var",
    "labels": { "host": "web-03.acme.example" },
    "annotations": { "runbook": "https://wiki.acme.example/disk" }
  }'

Resolve

curl -X POST 'https://evohub.io/ingest/alerts?key=YOUR_INTEGRATION_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "fingerprint": "web-03-disk-var", "status": "resolved" }'
Field Required Description
title To open an alert The alert's title.
description No The alert's description.
severity No critical, high, medium, low or info, in any letter case. error is read as high and warning as medium. Anything else, or nothing, is medium.
fingerprint No Identifies the alert within this integration. Repeats while it is open are recorded as Retriggered. Without one, EvoHub derives it from the title, the instance, every label and alert_service — see Alerts without a fingerprint.
status No resolved, ok, online or up resolves the open alert with the same fingerprint (or, without one, the same title, instance, labels and alert_service) instead of opening one.
source No Kept as the source label. The alert's source is always webhook.
labels, annotations No String-to-string maps, shown on the alert and usable in the voice template.
escalation_policy_id No Used only when the integration has no escalation policy of its own.

The key goes in ?key= only. A missing title (on a trigger), or a resolve with neither a fingerprint nor a title, returns 400 with the code VALIDATION_FAILED and the field in details; a body that is not a JSON object returns 400 with INVALID_BODY; a bad key returns 401 with INVALID_KEY. A body larger than 1 MiB returns 413 with PAYLOAD_TOO_LARGE. A successful call returns 200 with {"data": {"received": 1, "created": 1, "resolved": 0}, "success": true}; resolved is the number of alerts actually resolved, so a resolve that matched no open alert answers 200 with "resolved": 0. This format cannot acknowledge; use the API events above for that.

Alerts without a fingerprint

When a body has no fingerprint, EvoHub derives one from the title, the instance (alert_instance or the instance label), every label as key=value, and alert_service. Description, severity, alert_date and annotations are not part of it.

  • Sending exactly the same alert again retriggers the open alert instead of opening a second one. (Before October 2026 every such send opened a new alert.)
  • Alerts that differ in any label — another host, another region — are separate alerts.
  • A resolve without a fingerprint must carry the same title, instance, labels and alert_service the alert was opened with.

If you want to choose the grouping yourself, send a fingerprint.

Fingerprints belong to the integration

A fingerprint, sent or derived, only matches alerts opened through the same integration. Two integrations in one organization that send the same fingerprint open separate alerts, and neither can resolve the other's. EvoHub stores the fingerprint prefixed with the integration's ID; send it to this webhook as you always have, without the prefix.

Limits

Titles are kept to 250 characters and descriptions to 4,000. At most 50 labels and 50 annotations are kept (the first 50 by key), with keys up to 64 characters and values up to 512. Longer text is cut, not refused, so the alert still opens.

Moving from Parny

The generic webhook also reads the field names Parny's webhook uses. If a tool is already set up to send Parny's JSON, replace the Parny URL with your EvoHub webhook URL and leave the body as it is.

Parny field Becomes
alert_name Title
alert_description Description
alert_severity Severity (CRITICAL, HIGH, MEDIUM, LOW, INFO in any case)
alert_instance The instance label
alert_service The service label. On-Call does not route by service; use the integration's escalation policy for that.
alert_date The date label. The alert is stamped when it arrives.
alert_status resolved resolves the alert with the same alert_name, alert_instance and alert_service (and labels, if you send any).
curl -X POST 'https://evohub.io/ingest/alerts?key=YOUR_INTEGRATION_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "alert_name": "High CPU",
    "alert_severity": "HIGH",
    "alert_instance": "es-01",
    "alert_service": "Elasticsearch",
    "alert_description": "CPU above 95% for 10 minutes",
    "alert_date": "10.10.2026"
  }'

Send the same body with "alert_status": "resolved" to resolve it. When a body carries both an EvoHub field and its Parny counterpart (for example title and alert_name), the EvoHub field wins.

Tips

  • Choose a stable dedup_key (or fingerprint) per problem, such as <host>-<check>, so repeats while the problem lasts do not open new alerts and your resolve finds the right alert.
  • Send a resolve when your check passes again. Without one, the alert stays open until someone resolves it.
  • Only new alerts count toward usage; retriggers of an open alert do not. See How billing works.

Last updated