Integrations
Alert API and generic webhook
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.
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_keymatches 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_keythat has no open alert does nothing and still returns202. - 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, anotherregion— are separate alerts. - A resolve without a
fingerprintmust carry the same title, instance, labels andalert_servicethe 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(orfingerprint) 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.
Related
Was this page helpful?
