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

# Inbound webhooks

> Create and resolve scaling.cloud incidents from any monitoring tool by POSTing a signed JSON payload to a generic webhook.

The inbound webhook integration lets **any** tool that can send an HTTP request open and resolve scaling.cloud incidents — Datadog, Grafana, Prometheus Alertmanager, Sentry, a cron job, or your own service. You send a small JSON payload to a unique URL, signed with an HMAC secret, and scaling.cloud routes it to a [component](/concepts/components) and its escalation policy.

<Frame caption="An inbound webhook source connected to a scaling.cloud component">
  <img src="https://mintcdn.com/scalingcloud/5-FlXcH6ySIwHzG0/images/placeholder.svg?fit=max&auto=format&n=5-FlXcH6ySIwHzG0&q=85&s=18ab9c45531fe92b117df1f9652cb357" alt="Inbound webhook integration" width="1200" height="675" data-path="images/placeholder.svg" />
</Frame>

## How it works

1. You create an **inbound webhook** in scaling.cloud and pick the component its alerts affect. scaling.cloud issues a unique webhook URL and an HMAC signing secret, **both shown only once**.
2. Your tool sends a JSON payload to the URL on each alert, with an HMAC-SHA256 signature of the request body in the `X-Scaling-Signature` header.
3. A payload with `status: "open"` opens an incident on the mapped component (deduplicated by `dedupKey`). A payload with `status: "resolved"` and the same `dedupKey` resolves the matching open incident.

```
Monitoring tool → signed POST → scaling.cloud webhook → Incident created/resolved
```

## Endpoint

```
POST https://scaling.cloud/webhooks/inbound/{token}
Content-Type: application/json
X-Scaling-Signature: sha256=<hmac>
```

The `{token}` is part of the webhook URL shown when you create the source. No Clerk/session auth is used — the URL token identifies the source and the HMAC authenticates the payload.

## Payload

| Field         | Type                                              | Required | Description                                                                                                                                                                                                                    |
| ------------- | ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `title`       | string                                            | yes      | Incident title.                                                                                                                                                                                                                |
| `status`      | `"open"` \| `"resolved"`                          | yes      | `open` creates/keeps an incident; `resolved` resolves the open incident for the `dedupKey`.                                                                                                                                    |
| `dedupKey`    | string                                            | yes      | Grouping key. Repeated `open` events for the same key don't open duplicates; a `resolved` event with the same key resolves the incident it opened.                                                                             |
| `severity`    | `"critical"` \| `"high"` \| `"medium"` \| `"low"` | no       | Defaults to `high`.                                                                                                                                                                                                            |
| `description` | string                                            | no       | Longer detail; included in the incident body.                                                                                                                                                                                  |
| `resourceId`  | string                                            | no       | Affected resource identifier; included in the incident body.                                                                                                                                                                   |
| `component`   | string                                            | no       | Name or [alias](/concepts/components) of the component this alert is about. Resolved (case-insensitively) to the owning component, whose escalation policy is paged. Unrecognised values fall back to the webhook's component. |
| `links`       | array of `{ "title": string, "url": string }`     | no       | Up to 20 reference links (dashboards, runbooks).                                                                                                                                                                               |
| `eventId`     | string                                            | no       | Per-delivery idempotency key. If you resend the same `eventId`, it is processed at most once. When omitted, each request is treated as a new delivery.                                                                         |

```json Example open payload theme={null}
{
  "title": "Checkout error rate above 5%",
  "status": "open",
  "dedupKey": "checkout-5xx",
  "severity": "critical",
  "description": "5xx rate is 12% over the last 5 minutes.",
  "resourceId": "svc/checkout",
  "links": [{ "title": "Dashboard", "url": "https://grafana.example/d/checkout" }],
  "eventId": "alert-9f2c1a"
}
```

```json Example resolved payload theme={null}
{
  "title": "Checkout error rate recovered",
  "status": "resolved",
  "dedupKey": "checkout-5xx"
}
```

## Signing the request

Compute an HMAC-SHA256 of the **raw request body** with your signing secret and send it as `sha256=<hex>`:

```bash theme={null}
SECRET="whsec_…"            # the signing secret shown on create
URL="https://scaling.cloud/webhooks/inbound/scl_whk_…"
BODY='{"title":"Checkout error rate above 5%","status":"open","dedupKey":"checkout-5xx","severity":"critical"}'

SIG="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')"

curl -sS -X POST "$URL" \
  -H "Content-Type: application/json" \
  -H "X-Scaling-Signature: $SIG" \
  -d "$BODY"
```

<Warning>
  Sign the exact bytes you send. Re-serializing or pretty-printing the JSON after signing will change the body and the signature check will fail.
</Warning>

## Responses

| Status | Meaning                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | Processed. Body includes the `outcome` (`incident_opened`, `incident_resolved`, `noop_dedup`, `noop_no_open_incident`, `noop_replay`). |
| `400`  | The JSON body failed validation (missing/invalid fields).                                                                              |
| `401`  | Unknown URL token, or the `X-Scaling-Signature` did not match.                                                                         |
| `503`  | Transient — an incident for this `dedupKey` is mid-creation. Retry shortly.                                                            |

## State handling

| Event                               | What scaling.cloud does                       |
| ----------------------------------- | --------------------------------------------- |
| `open` (no incident for `dedupKey`) | Opens a new incident on the mapped component. |
| `open` (incident already open)      | No-op (deduplicated by `dedupKey`).           |
| `resolved` (incident open)          | Resolves the open incident.                   |
| `resolved` (nothing open)           | No-op.                                        |
| Repeat with the same `eventId`      | Idempotent — processed at most once.          |

The opened incident flows through the same pipeline as any other: escalation policy, on-call paging, Slack, and status pages.

## Security model

* **HMAC signature verification** — every request body is verified against the source's signing secret with a constant-time comparison. Requests with a missing or invalid signature are rejected with `401`.
* **Secrets revealed once** — the webhook URL token and signing secret are shown a single time on create. scaling.cloud stores only a SHA-256 hash of the token and an encrypted copy of the secret. If you lose the URL you must delete and recreate the source.
* **Rotate the secret** — you can rotate the signing secret at any time from the source's settings; the URL stays the same. Update your sender with the new secret.
* **No payload logging** — scaling.cloud does not log raw payloads at info level. Structured logs reference only `orgId` and `integrationId`.

<Warning>
  Treat the webhook URL and signing secret like credentials. Anyone who can post a validly-signed payload can open incidents in your organization. Rotate the secret if you suspect it has been exposed.
</Warning>

## Setup

<Steps>
  <Step title="Create the webhook in scaling.cloud">
    Go to **Settings → Integrations → Inbound webhooks** and click **New webhook**. Choose a name and the component its alerts should affect. Copy the webhook URL and signing secret on the one-time reveal screen.
  </Step>

  <Step title="Configure your tool">
    Point your monitoring tool's webhook/alert action at the URL. Send the payload above, signing the body with the secret and putting the result in `X-Scaling-Signature`.
  </Step>

  <Step title="Send a resolve on recovery">
    When the underlying condition clears, send a `status: "resolved"` payload with the same `dedupKey` so scaling.cloud auto-resolves the incident.
  </Step>
</Steps>

## One webhook, many components

Each inbound webhook has a **fallback component** — the one its alerts land on when nothing else is specified. To route a single webhook's alerts across many components, set the `component` field on each payload to the target component's name or one of its [aliases](/concepts/components); scaling.cloud resolves it to the owning component and pages that component's escalation policy. A `component` value that matches nothing falls back to the webhook's fallback component, so a typo never drops an alert.

## Removing a webhook

Deleting a webhook in scaling.cloud stops ingestion immediately — any further deliveries to its URL return `401`. Remember to remove the URL from the sending tool.
