# Uptime Kuma notifications on your phone: loud when it goes down, quiet when it comes back

> Send Uptime Kuma down and up alerts to iPhone and Android with the built-in Webhook notification. Outages arrive as high priority, recoveries arrive quietly, certificate expiry gets its own push, and retries keep a flapping check from flooding your phone.

*26.10.2026*

<!-- source: https://pocketalert.app/blog/uptime-kuma-notifications -->

Uptime Kuma is good at noticing that something is down. Where the notice ends up is another matter. Most setups send it to a Telegram group, a Discord channel or an email folder, next to everything else that buzzes your phone during the day. And the "back up" message makes exactly the same noise as the "down" one, so after a few flapping nights people mute the channel and stop reading it.

This guide sends Uptime Kuma's alerts to Pocket Alert as push notifications with different priorities. A monitor going down arrives as `high`, which gets through Focus on iPhone. A recovery arrives as `low`: it is in the feed when you look, and it doesn't wake anyone. Everything uses Uptime Kuma's built-in **Webhook** notification type, so there is nothing to install next to it.

Everything below was tested on Uptime Kuma 2.5.8 running in Docker.

## What you need

- Uptime Kuma 2.x. Most of this also works on 1.23.x; the differences are noted at the end.
- The Pocket Alert [app](/get-app) on your phone and an account. [Sign up](/signup) if you don't have one; the Free plan is enough to start.
- An API key from [API Keys](/account/apikeys) (route 1) or a webhook from [Webhooks](/webhooks) (route 2).

## Two ways to connect them

Uptime Kuma's Webhook notification can post to any URL, with either its own JSON or a body you write. That gives two routes:

<div class="cmp-table-wrap">
<table class="cmp-table">
<thead><tr><th></th><th>Route 1: Pocket Alert API</th><th>Route 2: Pocket Alert webhook URL</th></tr></thead>
<tbody>
<tr><th>Post URL</th><td><code>https://api.pocketalert.app/v1/messages</code></td><td><code>https://p4a.me/wh/&lt;slug&gt;</code></td></tr>
<tr><th>Push title</th><td>Yours, e.g. <strong>Down: Website</strong></td><td>Always <strong>Webhook &lt;name&gt;</strong></td></tr>
<tr><th>Down loud, Up quiet</th><td>Yes</td><td>Yes, with a custom body; otherwise one level for everything</td></tr>
<tr><th>Secret kept in Uptime Kuma</th><td>An API key with full access to your account</td><td>A URL that can only create pushes through that webhook</td></tr>
<tr><th>Request log in Pocket Alert</th><td>No</td><td>Yes, in <a href="/features/webhook-history">webhook history</a></td></tr>
</tbody>
</table>
</div>

**Pick route 1** if you want readable titles and the fewest moving parts. **Pick route 2** if you'd rather not store a full API key in Uptime Kuma (it keeps notification settings, headers included, as plain JSON in its database), or if you want every request it sends logged on the Pocket Alert side for debugging.

## Route 1: post straight to the Pocket Alert API

In Uptime Kuma, open **Settings → Notifications** and click **Set Up Notification**. Fill it in like this:

- **Notification Type:** Webhook
- **Friendly Name:** `Pocket Alert`
- **Post URL:** `https://api.pocketalert.app/v1/messages`
- **HTTP Method:** POST
- **Request Body:** Custom Body

Paste this into the body field, replacing `kuma.example.com` with the address of your Uptime Kuma:

```
{%- assign level = "default" -%}
{%- assign title = "Uptime Kuma" -%}
{%- assign text = msg -%}
{%- if heartbeatJSON -%}
  {%- assign text = heartbeatJSON.msg | default: msg -%}
  {%- if heartbeatJSON.status == 0 -%}
    {%- assign title = "Down: " | append: monitorJSON.name -%}
    {%- assign level = "high" -%}
    {%- for tag in monitorJSON.tags -%}
      {%- if tag.name == "critical" -%}{%- assign level = "critical" -%}{%- endif -%}
    {%- endfor -%}
  {%- else -%}
    {%- assign title = "Up: " | append: monitorJSON.name -%}
    {%- assign level = "low" -%}
  {%- endif -%}
{%- elsif msg contains "will expire" -%}
  {%- assign title = "Expiring soon" -%}
{%- endif -%}
{
  "title": {{ title | json }},
  "message": {{ text | json }},
  "level": "{{ level }}",
  "actions": [
    {
      "type": "view",
      "label": "Open in Uptime Kuma",
      "value": "https://kuma.example.com/dashboard/{{ monitorJSON.id }}"
    }
  ]
}
```

Then switch on **Additional Headers** and paste:

```json
{
  "Token": "YOUR_API_KEY",
  "Content-Type": "application/json"
}
```

What the body does:

- **The body is a Liquid template.** Uptime Kuma passes it `msg` (its ready-made text), `monitorJSON` (the monitor) and `heartbeatJSON` (the check that changed state). `heartbeatJSON.status` is `0` for down and `1` for up.
- **`| json` writes a complete JSON string, quotes included.** Error messages and monitor names can contain quotes and backslashes; with the filter they can't break the request. A monitor called `Bob's "main" site` still produces valid JSON.
- **Down is `high`, Up is `low`.** `high` is time-sensitive on iPhone and gets past Focus and the scheduled summary. `low` is delivered without interrupting you.
- **Tag a monitor `critical` to make it louder.** Add a tag named `critical` in Uptime Kuma to the monitors that matter most, and their outages are sent as `critical`, which plays through the silent switch and Do Not Disturb. Critical alerts are a paid feature; on the Free plan they are delivered as `high`.
- **Certificate and test messages have no heartbeat.** For those, the push uses Uptime Kuma's own text as the message. The same text is the fallback when a heartbeat message is empty, as on a ping monitor coming back up; the API rejects a push with an empty message.
- **The button opens the monitor.** `/dashboard/<id>` is the monitor's page in Uptime Kuma. For a certificate warning there is no monitor ID, and the button opens the dashboard instead. Action buttons are a paid feature; on the Free plan the API drops them and the push is delivered without one. See [action buttons](/features/action-buttons) for what else they can do.

**Don't skip the `Content-Type` header.** When Uptime Kuma posts a custom body, it labels it `application/x-www-form-urlencoded` unless you say otherwise. That is what it sent in my test. The Pocket Alert API only accepts JSON or multipart, and answers that request with `400`.

Click **Test**. A push titled "Uptime Kuma" with the text "Pocket Alert Testing" should arrive within a few seconds. Save.

Here is what a real outage and recovery sent from Uptime Kuma 2.5.8:

```json
{
  "title": "Down: Website",
  "message": "timeout of 4000ms exceeded",
  "level": "high",
  "actions": [
    { "type": "view", "label": "Open in Uptime Kuma", "value": "https://kuma.example.com/dashboard/1" }
  ]
}
```

```json
{
  "title": "Up: Website",
  "message": "200 - OK",
  "level": "low",
  "actions": [
    { "type": "view", "label": "Open in Uptime Kuma", "value": "https://kuma.example.com/dashboard/1" }
  ]
}
```

Use a separate API key named after Uptime Kuma, so you can revoke it on its own if that server is ever compromised.

## Route 2: a Pocket Alert webhook URL

Here Uptime Kuma posts its own JSON to a Pocket Alert webhook, and a template on the Pocket Alert side turns it into a push.

### Create the webhook

The button template can only be set through the API, so the quickest way is one call. It creates a webhook named "Uptime Kuma" with a message template, a default level of `high` (`1`) and an "Open in Uptime Kuma" button, then prints the receive URL:

```bash
PA_KEY='your-api-key'
KUMA_URL='https://kuma.example.com'

jq -n --arg kuma "$KUMA_URL" '{
  name: "Uptime Kuma",
  message: "%msg%",
  default_level: 1,
  actions: ([{type: "view", label: "Open in Uptime Kuma", value: ($kuma + "/dashboard/%monitor.id%")}] | tojson)
}' |
curl -sS https://api.pocketalert.app/v1/webhooks \
  -H "Token: $PA_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @- |
jq -r '"Receive URL: https://p4a.me/wh/" + .url'
```

If you'd rather click, create it in the console at [Webhooks](/webhooks): name **Uptime Kuma**, **Default priority** High, message `%msg%`. You get the same pushes without the button.

### Point Uptime Kuma at it

Set up a new notification of type **Webhook**, paste the receive URL as **Post URL**, and set **Request Body** to **Preset - application/json**. No headers are needed: the URL is the secret, so treat it like a password. Don't pick the multipart preset; Pocket Alert webhooks only read JSON.

Uptime Kuma sent this from my test setup when a monitor went down (trimmed; the `monitor` object has around a hundred fields):

```json
{
  "heartbeat": {
    "monitorID": 1,
    "status": 0,
    "time": "2026-10-31 16:09:56.331",
    "msg": "timeout of 4000ms exceeded",
    "important": true,
    "localDateTime": "2026-10-31 16:09:56"
  },
  "monitor": {
    "id": 1,
    "name": "Website",
    "url": "http://tls-target/",
    "type": "http",
    "tags": []
  },
  "msg": "[Website] [🔴 Down] timeout of 4000ms exceeded"
}
```

The template `%msg%` uses the top-level `msg`, because it is the one field present in every request. For certificate warnings and test messages, `heartbeat` and `monitor` are `null` and only `msg` is filled. The push arrives titled "Webhook Uptime Kuma" with "[Website] [🔴 Down] timeout of 4000ms exceeded" as the text. A placeholder whose path is missing renders as an empty string, so on a certificate warning the button points at `/dashboard/` and still works.

### Down loud, Up quiet on this route

With the JSON preset, every request gets the webhook's default level, so recoveries arrive as loud as outages. To fix that, switch **Request Body** to **Custom Body** and send a `level` field yourself. Pocket Alert webhooks read a top-level `level` from the payload before falling back to the default:

```
{
  "level": "{% if heartbeatJSON %}{% if heartbeatJSON.status == 0 %}high{% else %}low{% endif %}{% else %}default{% endif %}",
  "msg": {{ msg | json }},
  "monitor": { "id": "{{ monitorJSON.id }}" }
}
```

The body keeps the field names `msg` and `monitor.id`, so the webhook's message and button templates work unchanged. Pocket Alert webhooks read the body as JSON whatever its `Content-Type`, so this route needs no extra header.

## Turn it on for every monitor

A notification does nothing until a monitor uses it. At the bottom of the notification form there are two switches:

- **Default enabled** ticks this notification on every monitor you create from now on.
- **Apply on all existing monitors** adds it to all the monitors you already have. It runs once when you save and isn't remembered.

Turn on both the first time you save. To keep a monitor out, open it and untick the notification in its edit form.

## Certificate expiry warnings

Uptime Kuma can warn you before a TLS certificate runs out. Open an HTTP(s) monitor (Keyword and JSON Query monitors have it too) and tick **Certificate Expiry Notification** under **Advanced**. The box is greyed out while **Ignore TLS/SSL errors for HTTPS websites** is on.

The thresholds are global, under **Settings → Notifications → TLS Certificate Expiry**, and default to 7, 14 and 21 days. The warning goes to the same notifications as the monitor's outages, with text like:

```text
[TLS site][https://tls-target/] server certificate tls-target will expire in 4 days
```

The route 1 body catches it with `msg contains "will expire"` and titles it "Expiring soon" at `default` level. Uptime Kuma sends it once per threshold, not on every check: in my test the monitor was checked 119 times with a certificate four days from expiry, and exactly one warning went out.

## Keep a flapping monitor from flooding your phone

Three monitor settings decide how much noise an unstable service makes:

<div class="cmp-table-wrap">
<table class="cmp-table">
<thead><tr><th>Setting</th><th>What it does</th><th>Suggested</th></tr></thead>
<tbody>
<tr><th>Retries</th><td>Failed checks marked <strong>Pending</strong> before the monitor counts as down. Pending checks don't notify.</td><td><code>2</code> or <code>3</code></td></tr>
<tr><th>Heartbeat Retry Interval</th><td>Seconds between checks while pending.</td><td><code>20</code> to <code>60</code></td></tr>
<tr><th>Resend Notification if Down X times consecutively</th><td>Repeats the down alert after this many more failed checks. <code>0</code> sends it once.</td><td><code>0</code>, or a value that works out to 30 to 60 minutes</td></tr>
</tbody>
</table>
</div>

With **Retries** set to 2 in my test, the first two failed checks were marked pending and only the third sent "Down". With **Resend** set to 3, the down alert repeated after every three more failed checks until the service came back. The resend count is in checks, not minutes. With a 60-second interval, 30 means a reminder every half hour.

Every down, every up and every resend is one message. The Free plan allows 50 a day, so one flapping monitor with no retries can use it up in an afternoon. Retries are the cheapest fix.

## If nothing arrives

- **The test fails with HTTP 400.** On route 1, check that **Additional Headers** includes `"Content-Type": "application/json"`. Without it the request goes out as form data.
- **HTTP 401.** The `Token` header is missing or the key was revoked. Create a new one under [API Keys](/account/apikeys).
- **The test arrives but outages don't.** Open the monitor and check that the notification is ticked. Also, Uptime Kuma doesn't notify on a monitor's first check if it is up, so a new monitor stays quiet until something changes.
- **Route 2 shows nothing.** Open the webhook's history in the console. If the request is there with an error, it says why: invalid JSON, or a template that produced an empty message. If nothing is recorded at all, Uptime Kuma never reached `p4a.me`. You can also send requests to the [webhook tester](/tools/webhook) to see exactly what Uptime Kuma posts.
- **A recovery seems missing.** `low` pushes are silent. Check the feed in the app.

## FAQ

### Which Uptime Kuma version does this need?

It was tested on 2.5.8, the latest release at the time of writing. Version 1.23.x also has the Webhook type with Custom Body and Additional Headers, and its template gets the same `msg`, `monitorJSON` and `heartbeatJSON` variables. I rendered the route 1 body with the template engine that ships in 1.23.16 and got the same JSON, but the full end-to-end run was on 2.5.8 only.

### What if the server running Uptime Kuma goes down?

Then nothing sends the alert. Uptime Kuma can't report its own outage, so run something outside it that notices, like a check from a different machine or a hosted monitor. The [uptime monitoring](/use-cases/uptime-monitoring) page lists hosted monitors that work with Pocket Alert webhooks.

### Can I send different monitors to different phones?

Yes, on a paid plan. Create a notification per group with a different `device_id` in the body (route 1) or a different webhook with its own device (route 2), and tick the right one on each monitor. The Free plan has one device and one webhook, so there is only one phone to send to.

### Does this work for ping, port and DNS monitors?

Yes. The body only uses the monitor's name and ID and the heartbeat's status and message, which every monitor type has. Some types, ping among them, leave the message empty when the check succeeds; the body then falls back to Uptime Kuma's own text. The button links to the monitor's page in Uptime Kuma rather than the checked address, so it works even when the address isn't a URL.

### I already use this for Proxmox. Can it share an API key?

It can, but give Uptime Kuma its own key so revoking one doesn't break the other. The [Proxmox guide](/blog/proxmox-alerts) covers backup and disk alerts from the same homelab.

[Create a free account](/signup), paste the body into a Webhook notification, and let the next outage reach your phone while the recovery waits quietly in the feed.
