# Set up a webhook alert contact

A webhook contact posts the alert to a URL you control, as JSON, so you can feed it into your own system,
automation or bot. Under the hood this is a plain `http` contact with no `gateway` value - see
[Slack](/alerts/channels/slack/), [Teams](/alerts/channels/teams/) and the other gateway-recognized flavors if
you're integrating with one of those services instead of your own endpoint.

## Settings reference

| Setting (UI label) | API field (v2) | Type / allowed values | Default | Plan limits | What it does for you |
|---|---|---|---|---|---|
| Contact Type | `type` | `"http"` | - | - | |
| Webhook URL | `address` | Any URL your endpoint listens on | - (required) | - | Where the alert is POSTed |
| Request format | `mimeType` | `application/json` (default), `application/x-www-form-urlencoded`, `text/xml`; `text/html`/`text/plain` once a custom template exists | `application/json` | - | The outgoing request's `Content-Type` and body encoding |
| HTTP headers | `httpHeaders` | Array of `{header, value}` | Empty | - | Custom headers sent with every request (an `Authorization` token, a shared secret, a routing header) |
| Group events | `groupedAlerts` | boolean | `false` | - | Combine simultaneous alerts into one request instead of one each - see below and [grouped alerts](/alerts/grouped-alerts/) |
| Custom templates | `templates` | Array of `{event, content}`, `event` = `up`/`down`/`repeatedlyDown` | Empty (uses the built-in payload) | - | Author the exact body HostTracker sends per event, with `[[token]]` placeholders |

## Set it up in the app

1. Open **Alerts & Contacts** and click **+ Add contact**.
2. Choose **HTTP (webhook)** in the Contact Type list.
3. Enter the **Webhook URL** that should receive the alert, and give the contact a name.

![The webhook contact editor: type, Webhook URL, and the Request Template Configuration group.](../../../../assets/screenshots/contact-editor-webhook.png)

## Request Template Configuration

Expand **Request Template Configuration** to shape the outgoing request:

![The Request Template Configuration group: HTTP headers, request format, group events and body template.](../../../../assets/screenshots/contact-editor-webhook-template.png)

- **HTTP headers** - add custom request headers. Click **Add header** for each.
- **Request format** - the body's content type, `application/json` by default.
- **Group events** - when on, several alerts that fire together are delivered as one combined request (a JSON
  array of events) instead of one request each - see [grouped alerts](/alerts/grouped-alerts/).
- **Request body template** - either **Predefined templates** (ready-made payload shapes) or **Manually
  defined**, where you write the JSON yourself with `[[token]]` placeholders that HostTracker fills at send
  time.

## What the message looks like, and the template variables

Without a custom template, HostTracker posts its own built-in JSON payload describing the event. With a custom
template, you write the exact body - one template per event type (Up/Down/RepeatedlyDown) - and drop in
variables like `[[taskName]]`, `[[taskUrl]]`, `[[error]]`, `[[downtimeFormated]]` or `[[failedLocations]]`
anywhere in the text. A variable with no value for that event resolves to an empty string rather than leaking
the literal token, and values are escaped for whichever `mimeType` you chose (JSON string escaping for
`application/json`, URL-encoding for the form-urlencoded type, and so on).

Example Down template:

```json
{"event":"down","site":"[[taskName]]","url":"[[taskUrl]]","error":"[[error]]","code":"[[errorcodename]]","details":"[[currentCheckDetailsLink]]"}
```

**Custom-template requests are always sent individually** - grouping never combines them, since the point of a
custom template is to describe exactly one event's shape.

The most commonly used variables (availability: **U** = Up, **D** = Down, **R** = RepeatedlyDown):

| Variable | U | D | R | Meaning |
|---|:-:|:-:|:-:|---|
| `[[taskName]]` | Yes | Yes | Yes | Name of the monitor |
| `[[taskUrl]]` | Yes | Yes | Yes | The monitored URL/host |
| `[[taskEditLink]]` | Yes | Yes | Yes | Link to edit the monitor in HostTracker |
| `[[eventUrl]]` | Yes | Yes | Yes | Link to the event/incident details |
| `[[timeOfSend]]` | Yes | Yes | Yes | When the notification was sent (your profile's date/time format) |
| `[[error]]` | Yes | Yes | Yes | Last error message |
| `[[errorcodename]]` | Yes | Yes | Yes | Short stable error code (e.g. `ConnectTimeout`, `Http503`) |
| `[[firstErrorTime]]` | Yes | | Yes | When the outage started |
| `[[downtimeInSeconds]]` | Yes | Yes | Yes | Downtime in seconds since the first failed check |
| `[[downtimeFormated]]` | Yes | Yes | Yes | Human-formatted downtime (e.g. "1 hour 24 minutes") |
| `[[failedLocations]]` | Yes | Yes | | Locations that failed during the state-change recheck |
| `[[failedLocationsCount]]` / `[[okLocationsCount]]` / `[[totalLocationsCount]]` | Yes | Yes | | Recheck location counts |

A further set covers certificate-monitoring events (`[[certExpirationDateUtc]]`, `[[certDaysToExpiration]]`,
`[[certEvents]]`) and registry-blacklist events (`[[rknBlocks[0].case]]` and siblings) - these resolve to an
empty string on every other event type.

## Do it with the API or MCP

```
POST /contact
{ "type": "http", "address": "https://example.com/hooks/hosttracker", "name": "My webhook" }
```

With headers and a custom Down template:

```
PATCH /contact/{id}
{
  "httpHeaders": [ { "header": "Authorization", "value": "Bearer <token>" } ],
  "templates": [ { "event": "down", "content": "{\"site\":\"[[taskName]]\",\"error\":\"[[error]]\"}" } ]
}
```

The `create_contact` MCP tool deliberately excludes `http` - use `api_request` with the body above for the
contact-based webhook covered on this page. For the separate, **signed** webhook resource with typed events
and delivery logs, use `create_webhook` instead (see below).

## What happens next

An `http` contact is born confirmed - there's nothing to verify out-of-band, since a real delivery to the URL
is itself the proof. [Send a test](/alerts/test-a-contact/) to confirm your endpoint receives and accepts it.

## Limits and gotchas

- A contact-based webhook is **unsigned** (legacy) - if you need to verify the request came from HostTracker,
  use the [signed v2 webhooks](/integrations/webhooks/) instead.
- Slack, Teams, PagerDuty, Opsgenie, Pushover, Pushbullet and Mattermost webhook URLs/keys are recognized as
  their own channel types (with rich formatting) rather than a generic webhook - see their own setup pages if
  that's what you're connecting.
- A malformed `httpHeaders`/`templates` entry (missing `header` or `event`) is refused with `422`, not silently
  dropped.
- **A plain webhook (no recognized gateway) does not receive certificate-expiry, domain-expiry, DNSBL
  blacklist or Web Risk notices** - those four check-specific alert kinds are only delivered to email, SMS,
  voice, chat/bot channels, or an `http` contact whose `gateway` is one of the seven recognized ones (Slack,
  Teams, PagerDuty, Opsgenie, Pushover, Pushbullet, Mattermost). Ordinary monitor Down/Up/RepeatedlyDown alerts
  are unaffected by this and reach a plain webhook normally. If you need those specific notices on your own
  endpoint, subscribe an email contact to them as well, or route through one of the recognized gateways.

:::note[Looking for the full webhooks system?]
This page covers the simple **contact-based** webhook, which fires per alert like any other channel. For the
richer, HMAC-**signed** webhook system with typed events (monitor alerts, incidents, expiry notices), retries and
delivery logs, see [Webhooks](/integrations/webhooks/) under Integrations.
:::

## Related

- [Webhooks (integrations)](/integrations/webhooks/)
- [Slack](/alerts/channels/slack/)
- [All channels](/alerts/channels/)
