---
title: "Webhook"
description: "Send Middleware alert notifications to any HTTP endpoint with custom headers, OAuth 2.0 auth methods and a custom JSON payload built from alert variables."
section: "Integrations"
breadcrumb: "Integrations > Alerting & Collaboration > Webhook"
canonical: "https://docs.middleware.io/integrations/webhook-integration"
---
# Webhook

Send Middleware alert notifications to any HTTP endpoint. Use a webhook for tools that don't have a dedicated Middleware integration, such as Zoho Cliq, an internal service, or an automation pipeline.

## What you get

- Send alerts with `POST`, `PUT` or `PATCH` to any `http` or `https` URL
- Authenticate with custom headers, query parameters, or an OAuth 2.0 token that Middleware refreshes for you
- Shape the request body to match what the receiver expects, using alert variables such as `{{title}}` and `{{status}}`
- Send a test request and see the receiver's real status code and response before you save
- See what the receiver replied for every alert, in the rule's alert history
- Choose one or more webhooks as a notification medium per alert rule

## Prerequisites

- A Middleware workspace with permission to install integrations and create alerts
- An HTTP endpoint that accepts requests from Middleware, plus the credentials it requires (an API key, a token, or OAuth 2.0 client credentials)

## How it works

The Webhook integration has two tabs:

- **Webhooks** are destinations: a URL, a method, headers, and an optional custom payload. Alert rules send to webhooks.
- **Auth Methods** are reusable OAuth 2.0 credentials. A webhook can reference an auth method, and Middleware attaches a fresh access token to every request.

Keeping credentials in auth methods lets several webhooks share one credential, and rotating it is a single change. Webhooks and auth methods belong to a project, and every alert rule in that project can use them.

## Step-by-step Setup

#### 1 Add a webhook

- In Middleware, go to **Integrations**, search "Webhook", and open it.
- On the **Webhooks** tab, click **Add webhook**.
- Fill in the form:

| Field | Description |
| --- | --- |
| **Name** | Required. The name you pick when you add the webhook to an alert rule, for example `Production alerts`. |
| **Method** | `POST` (default), `PUT` or `PATCH`. |
| **URL** | Required. The full `http://` or `https://` URL of the receiver. |
| **Request Headers** | Optional headers sent with every request, for example `X-API-Key: <key>`. |
| **Query Parameters** | Optional parameters added to the URL, for receivers that authenticate with an API key in the query string. |
| **Auth Method** | Optional. Attaches an OAuth 2.0 token to every request. See step 3 below. |
| **Custom Payload** | Optional request body built from [alert variables](#payload-variables). Leave it empty to send the [default payload](#default-payload). |

![Add webhook form with a secret header, a custom payload and a successful Send test](/images/v2/integration/webhook-add-form.png)

**Keep credentials secret.** Select **Secret** on a header or query parameter to store its value write-only:

- The value is never shown again in the UI or returned by the API. Only a mask is displayed.
- Leaving it blank when you edit the webhook keeps the stored value. Typing a new value replaces it.
- It is removed from any error message or response body shown in Middleware. In the screenshot above, the receiver echoed the header back and Middleware shows it as `••••`.

Select **Secret** before you type the value: selecting it clears anything already in the field.

> **Warning:** **Don't put tokens in the URL.** Anything left in the **URL** field is stored and displayed in plain text. If the receiver expects a token in the query string, add it under **Query Parameters** and mark it **Secret**. Middleware warns you when the URL contains a query string.

#### 2 Send a test and save

- Click **Send test**. Middleware sends a sample request and shows the receiver's status code, response body and duration.
- Fix anything the receiver rejects, then click **Save**.

![Send test result showing HTTP 200 and the receiver's response body](/images/v2/integration/webhook-send-test.png)

The webhook appears in the list with an **Enabled** toggle. Turn it off to pause delivery without editing any alert rules: rules skip disabled webhooks.

![Webhooks tab listing a saved webhook with its Enabled toggle](/images/v2/integration/webhook-list.png)

#### 3 (Optional) Add an auth method

Use an auth method when the receiver needs an OAuth 2.0 access token. Access tokens usually expire within an hour, so a token pasted into a header stops working soon after. An auth method gets a new token before the old one expires and caches it until then.

- Open the **Auth Methods** tab and click **Add auth method**.
- Fill in the form:

| Field | Description |
| --- | --- |
| **Name** | Required. Shown in the webhook's **Auth Method** dropdown. |
| **Grant type** | **OAuth 2.0 — Client Credentials**: the client ID and secret are exchanged for a token directly. **OAuth 2.0 — Refresh Token**: a refresh token you obtained once is exchanged for new access tokens. Use this when your provider issues user-authorized refresh tokens. |
| **Access Token URL** | Required. The provider's token endpoint, for example `https://<provider>/oauth/token`. |
| **Client ID** | Required. |
| **Client Secret** | Required when you create the auth method. Stored write-only. |
| **Refresh Token** | Shown only for the Refresh Token grant. Stored write-only. |
| **Scope** | Optional. Sent to the provider exactly as typed. Separate multiple scopes the way your provider expects: a space for most providers, a comma for some (such as Zoho). |
| **Token Prefix** | Defaults to `Bearer`. Change it only if your provider uses a different scheme. |
| **Header Name** | Defaults to `Authorization`. |

The token is sent as `<Token Prefix> <token>` in the header you named. For most providers that is `Authorization: Bearer <token>`.

- Click **Test credentials**. Middleware requests a token and shows **Token obtained** with its expiry, or **Failed** with the provider's error.
- Click **Save**.
- Open the webhook that should use these credentials, select the auth method under **Auth Method**, and click **Save**.

![Auth method form using the client credentials grant, with Token obtained after Test credentials](/images/v2/integration/webhook-auth-method.png)

> **Note:** **Note**: **Test credentials** only proves Middleware can get a token. Use **Send test** on the webhook to confirm the receiver accepts it. When a webhook uses an auth method, remove any `Authorization` header you added by hand: the auth method sets that header on every request.

#### 4 Use the webhook when creating an alert

- Create (or edit) an alert in **Alerts** → **Create Alert**.
- In **Aggregation & Medium**, select the **Webhook** medium.
- Choose how to send:

  - **Use a configured webhook**: pick one or more webhooks saved on the Webhook integration. Their headers, auth method and custom payload are used. If you just created a webhook in another tab, click the refresh icon beside the dropdown to load it.
  - **Enter a webhook URL**: type a URL directly. The request has no custom headers or authentication and uses the default payload. If the URL is a Slack incoming webhook (`hooks.slack.com/services/...`), Middleware sends a Slack-formatted message instead. Use this for quick tests only.
- Optionally select **Get a notification when the alert is resolved** and **Get a notification when status no data**.
- Save the alert.

![Selecting a configured webhook as the notification medium on an alert rule](/images/v2/integration/webhook-alert-medium.png)

## Custom payload

By default, Middleware sends a fixed JSON body (see [Default payload](#default-payload)). Most chat tools and APIs expect a specific body instead. Use **Custom Payload** to write that body yourself.

Put variables in double curly braces. When an alert fires, Middleware replaces each one with the alert's value:

```json
{
  "text": "[{{status}}] {{title}}",
  "rule": "{{rule_name}}",
  "value": "{{value}}",
  "threshold": "{{threshold}}",
  "link": "{{link}}"
}
```

- Values are escaped for JSON strings, so a quote or line break in a rule name won't break the body. Place variables inside quoted strings, as shown above.
- An unknown or misspelled variable is replaced with an empty string.
- The body is sent as written and isn't checked for valid JSON, because not every receiver expects JSON. If the receiver rejects it, **Send test** shows you its error.
- Type `{{` in the editor to pick from the list of variables. The editor may display a variable with spaces, such as `{{ status }}`. Both forms work.

### Payload variables

| Variable | Description |
| --- | --- |
| `{{rule_name}}` | Name of the alert rule. |
| `{{title}}` | Notification title. |
| `{{summary}}` | One-line summary of the alert. |
| `{{body}}` | Notification body. When the rule has a custom alert message, this is that message with its variables filled in. |
| `{{status}}` | `Critical`, `Warning`, `Okay` or `No data`. |
| `{{status_label}}` | Status as shown in notification headers. |
| `{{monitor_type}}` | Monitor type, for example metric, log or APM. |
| `{{metric_name}}` | Metric or formula that was evaluated. |
| `{{operator}}` | Comparison operator, for example `>`. |
| `{{condition_phrase}}` | The condition written out in words. |
| `{{threshold}}` | Threshold that was crossed. |
| `{{value}}` | Evaluated value as a raw number. |
| `{{value_formatted}}` | Evaluated value with its unit. |
| `{{warning_threshold}}` | Warning threshold configured on the rule. |
| `{{critical_threshold}}` | Critical threshold configured on the rule. |
| `{{evaluation_time}}` | Evaluation window of the rule. |
| `{{alert_on}}` | Time the alert was triggered. |
| `{{host}}` | Host the alert is about, when it applies. |
| `{{group_name}}` | Attribute the rule groups by, for example `host.name`. |
| `{{group_value}}` | Value of that attribute for this alert, for example `web-01`. |
| `{{triggered_at}}` | Time the alert was triggered. |
| `{{link}}` | Link to the alert in Middleware. |

Rules that send one notification for many groups also provide `{{group_count}}` (number of groups alerting) and `{{top_offenders}}` (the groups with the highest values).

### Test payload

The **Test payload (this test only)** box below Custom Payload controls what **Send test** sends. It starts with your custom payload, and you can change it for a single test without changing the saved payload. Click **Reset to payload above** to go back to it.

During a test, variables are filled with sample values, for example `{{title}}` becomes `High CPU on web-01` and `{{value}}` becomes `97.4`. Some variables, such as `{{value_formatted}}`, have no sample value and are empty in a test. They are filled in when a real alert fires.

### Default payload

When **Custom Payload** is empty, Middleware sends a JSON object containing the full notification, a timestamp and a version:

```json
{
  "notification": {
    "RuleName": "High CPU",
    "Title": "High CPU on web-01",
    "Summary": "...",
    "MonitorType": "metric",
    "Status": "Critical",
    "MetricName": "system.cpu.utilization",
    "Operator": ">",
    "Threshold": "90",
    "Value": "97.4 %",
    "Link": "https://<your-project>.middleware.io/alerts/rules?ruleid=...",
    "Host": "web-01",
    "GroupName": "host.name",
    "GroupValue": "web-01",
    "TriggeredAt": "...",
    "...": "..."
  },
  "timestamp": "2026-10-30T09:15:00Z",
  "version": "1.0"
}
```

Use **Send test** with an empty Custom Payload to see the complete default body.

## Verify the Integration

Middleware records what each webhook replied every time an alert fires.

- Open the alert rule from **Alerts** → **Rule List**.
- In **Alert History**, the **Webhook** column shows one badge per webhook:

  - `<webhook name> · <status code>`, green for a `2xx` response and red otherwise.
  - `<webhook name> · failed` when the request couldn't be completed at all, for example a DNS error, a TLS error, a timeout, or an auth method that couldn't get a token.
- Hover over a badge to see the receiver's response body (first 1 KB) or the error, plus the request duration.

![Alert History with the Webhook column showing a delivered webhook and its status code](/images/v2/integration/webhook-alert-delivery.png)

Any `2xx` status counts as delivered. Some receivers, such as Zoho Cliq, return `204 No Content` on success. Each webhook on a rule is delivered independently, so one failing webhook doesn't stop the others.

> **Note:** **Note**: If you remove a webhook, alert rules that use it stop sending to it. Before you remove an auth method, update every webhook that uses it.

## Example: Zoho Cliq

This example posts alerts to a Zoho Cliq channel using an OAuth auth method.

#### 1 Create a Zoho API client

In the [Zoho API Console](https://api-console.zoho.com/), create a **Self Client** or **Server-based Application** and copy its client ID and client secret.

#### 2 Add an auth method

| Field | Value |
| --- | --- |
| Grant type | OAuth 2.0 — Client Credentials |
| Access Token URL | `https://accounts.zoho.<dc>/oauth/v2/token` |
| Scope | `ZohoCliq.Messages.CREATE,ZohoCliq.Channels.READ` (Zoho expects scopes separated by commas, not spaces) |
| Token Prefix | `Zoho-oauthtoken` |
| Header Name | `Authorization` |

`<dc>` is your Zoho data center: `com` (US), `in` (India), `eu` (Europe) or `com.au` (Australia). It must match the data center your organization is on. Click **Test credentials** to confirm Middleware can get a token.

#### 3 Add a webhook

| Field | Value |
| --- | --- |
| Method | `POST` |
| URL | `https://cliq.zoho.<dc>/api/v2/channelsbyname/<channel-unique-name>/message` |
| Auth Method | The auth method from step 2 |
| Custom Payload | `{"text": "[{{status}}] {{title}}\n{{summary}}\n{{link}}"}` |

The token URL (`accounts.zoho.<dc>`) and the message URL (`cliq.zoho.<dc>`) are on different hosts. Use the same data center for both.

The channel's unique name is not its display name: Cliq removes hyphens and spaces, so a channel shown as `#mw-alerts` has the unique name `mwalerts`. To find it, open the channel and go to **Channel Info → Connectors → API Endpoint**.

#### 4 Test and save

Click **Send test**. A successful post returns `204` and the message appears in the channel. Click **Save**, then add the webhook to your alert rules.

## Troubleshooting

- **401 or 403 from the receiver**: The receiver rejected the credentials. Use **Send test** to see the exact response body, then check the header or query parameter the receiver expects. With an auth method, check the **Token Prefix** (Zoho requires `Zoho-oauthtoken`, not `Bearer`) and that you removed any manual `Authorization` header.
- **"auth method error" on the badge**: Middleware couldn't get a token, so the request wasn't sent. Open the auth method and click **Test credentials** to see the provider's error. Common causes are a wrong **Access Token URL**, an expired refresh token, a rotated client secret, or scopes in the wrong format.
- **Send test works but alerts don't arrive**: **Send test** uses the form as it is on screen, but alerts use the saved webhook, so make sure you clicked **Save**. Check that the webhook is **Enabled**, and that the alert rule selects this webhook under **Aggregation & Medium**. If the **Webhook** column in Alert History has no badge, the rule didn't send a notification, for example because resolve notifications are off.
- **Variables are empty**: Check the spelling against the [variable list](#payload-variables). Some variables only apply to some rules, for example `{{host}}` is empty for rules that aren't about a host.
- **404 from the receiver**: Check the URL path and host. For Zoho Cliq, a 404 with `request_url_invalid` usually means the wrong data center host, or the channel's display name used instead of its unique name.
- **Token rejected even though it's fresh**: Tokens are scoped to the provider's region. A token issued by one Zoho data center is rejected by another.

Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us page or join our Slack channel.
