> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aciona.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Generic webhook

> Send alerts from any system with a simple JSON POST.

## Overview

The generic webhook accepts simple JSON from any system that can `POST` — scripts, CI/CD, internal jobs, tools with no dedicated integration.

|                     |              |
| ------------------- | ------------ |
| **Source type**     | `Generic`    |
| **Mechanism**       | JSON `POST`  |
| **Correlation**     | `externalId` |
| **Auto-resolution** | —            |

<Warning>
  The generic webhook **does not resolve incidents automatically**. It always opens or aggregates. If your tool has a dedicated source type (Grafana, Prometheus, Datadog, CloudWatch, Zabbix, New Relic), use that instead — you get auto-resolution and much better field mapping.
</Warning>

## 1. Create the alert source

Under **Alert sources**, create a source of type **Generic**. Copy the URL and the token.

## 2. Send the alert

```bash theme={null}
curl -X POST "https://ingress.aciona.me/webhooks/<alertSourceId>" \
  -H "Content-Type: application/json" \
  -H "X-Aciona-Token: <sourceToken>" \
  -d '{
    "externalId": "nightly-etl-job",
    "title": "Nightly ETL job failed",
    "description": "Exited with code 1 after 3 retries",
    "severity": "high",
    "service": "data-pipeline",
    "status": "firing",
    "labels": { "env": "prod", "job": "nightly-etl" },
    "url": "https://ci.example.com/builds/4821"
  }'
```

Expected response: `202 Accepted`.

## Accepted fields

| Field         | Type   | Required    | Description                                                             |
| ------------- | ------ | ----------- | ----------------------------------------------------------------------- |
| `title`       | string | recommended | Incident title. Without it, becomes `Untitled alert`.                   |
| `description` | string | no          | Detail of the problem.                                                  |
| `severity`    | string | no          | `critical`, `high`, `warning`, `info` or a synonym. Default: `warning`. |
| `service`     | string | recommended | Service name in aciona.me. Also accepted as `labels.service`.           |
| `externalId`  | string | recommended | Stable identifier of the problem. It is the correlation key.            |
| `status`      | string | no          | `firing` or `alerting`. Any other value makes the alert **ignored**.    |
| `labels`      | object | no          | Free-form labels, preserved on the incident.                            |
| `url`         | string | no          | Link back to the origin. Also accepted as `externalUrl`.                |

<Note>
  If `status` is **absent**, the alert is treated as a trigger. If it is present with a value other than `firing` or `alerting`, the alert is accepted with `202` but **no incident is created** — including for `resolved`.
</Note>

## Grouping

Repeated sends with the same `externalId` for the same service are **aggregated** into the open incident instead of creating duplicates. Use a stable `externalId` per problem — the job name, the check identifier, the rule key.

<Tip>
  If you omit `externalId`, correlation falls back to a signature computed from the content. Changes to the title or description then produce separate incidents. Send an `externalId` whenever you can.
</Tip>

## Examples

<CodeGroup>
  ```python Python theme={null}
  import os
  import requests

  requests.post(
      f"https://ingress.aciona.me/webhooks/{os.environ['ACIONA_SOURCE_ID']}",
      headers={"X-Aciona-Token": os.environ["ACIONA_TOKEN"]},
      json={
          "externalId": "daily-backup",
          "title": "Daily backup failed",
          "description": "pg_dump returned code 2",
          "severity": "critical",
          "service": "primary-database",
          "status": "firing",
          "labels": {"env": "prod"},
      },
      timeout=10,
  )
  ```

  ```javascript Node.js theme={null}
  await fetch(
    `https://ingress.aciona.me/webhooks/${process.env.ACIONA_SOURCE_ID}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Aciona-Token': process.env.ACIONA_TOKEN,
      },
      body: JSON.stringify({
        externalId: 'daily-backup',
        title: 'Daily backup failed',
        severity: 'critical',
        service: 'primary-database',
        status: 'firing',
      }),
    },
  );
  ```
</CodeGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Returned 202 but no incident appeared" icon="ghost">
    The `status` field probably has a value other than `firing` or `alerting` — in which case the alert is accepted and discarded. Send `firing` or omit the field.
  </Accordion>

  <Accordion title="Duplicate incidents" icon="copy">
    The `externalId` is changing between sends (for example, by including a timestamp). Use a stable identifier per problem.
  </Accordion>

  <Accordion title="Incident with no service or no owner" icon="user-x">
    The `service` field does not match any service in the organization, or the service has no responsible team with an active schedule.
  </Accordion>

  <Accordion title="How do I resolve the incident?" icon="check-check">
    From the dashboard or the app. The generic webhook does not auto-resolve.
  </Accordion>
</AccordionGroup>

<Card title="Full payload reference" icon="list" horizontal href="/en/reference/generic-webhook-payload" />
