SMS alerts from Uptime Kuma and Grafana, sent from your own number

Checked against the docs:

In Uptime Kuma you add a Webhook notification: Post URL https://smsportal.app/api/v1/gateway/send-sms, Request Body set to Custom Body with JSON containing recipients and message, and Additional Headers with x-api-key and Content-Type: application/json. Grafana 12 and Healthchecks.io do the same with their own webhooks. The alert goes out as an SMS from your Android phone.

What you need

  • Uptime Kuma 1.23 or newer (the Custom Body option in the Webhook notification).
  • For Grafana: self-managed Grafana 12 or newer (Extra Headers and Custom Payload in the Webhook contact point).
  • A smsportal account with an Android phone and SIM card connected, plus an API key (API Keys → Generate API Key).
  • The monitoring tool runs outside the server it watches (otherwise it goes down with it).

How to send an SMS alert from Uptime Kuma

  1. Create a Webhook notification

    In Uptime Kuma open Settings → Notifications → Set Up Notification. Notification Type: Webhook, Friendly Name such as SMS smsportal, Post URL: https://smsportal.app/api/v1/gateway/send-sms. If your version has an HTTP Method field, keep POST.

  2. Paste a custom body

    Under Request Body choose Custom Body and paste the template. It builds a short DOWN: name - reason or UP: ... text, and the json filter turns it into a valid JSON string, so a quote in a monitor name cannot break the request. The text is cut at 160 characters.

    Custom Body
    {% capture text %}{% if heartbeatJSON %}{% if heartbeatJSON.status == 0 %}DOWN{% else %}UP{% endif %}: {{ monitorJSON.name }} - {{ heartbeatJSON.msg }}{% else %}{{ msg }}{% endif %}{% endcapture %}
    {
      "recipients": ["+14155550123"],
      "message": {{ text | strip | truncate: 160 | json }}
    }
  3. Add the headers

    Turn on Additional Headers and paste the JSON below. Content-Type matters: Uptime Kuma sends a Custom Body as plain text without a JSON type, and without this header the API cannot read recipients and message.

    Additional Headers
    {
      "x-api-key": "YOUR_API_KEY",
      "Content-Type": "application/json"
    }
  4. Test and assign to monitors

    Click Test: you get the text SMS smsportal Testing. Save. Default enabled turns the notification on for new monitors and Apply on all existing monitors for current ones. Set Retries to 1 or 2 on the monitor so a brief hiccup does not wake you at night.

  5. Grafana: Webhook contact point

    In Grafana open Alerting → Contact points → Add contact point, Integration: Webhook, URL as above. Under Optional Webhook settings add an x-api-key header with your key in Extra Headers, and paste the template into Payload Template under Custom Payload. Route alerts to it in Notification policies. On Grafana Cloud Custom Payload is not generally available yet: send the webhook to n8n and text from there.

    Grafana: Payload Template
    {{ coll.Dict "recipients" (coll.Slice "+14155550123") "message" (printf "Grafana %s: %s" (toUpper .Status) .CommonLabels.alertname) | data.ToJSON }}
  6. Healthchecks.io: Webhook integration

    In your project open Integrations → Webhook. Under Execute when a check goes down pick POST, enter the API URL and fill in Request Body and Request Headers (one header per line). You can leave the up section empty. $NAME is the check name: keep quotes out of it, or the JSON breaks.

    Healthchecks.io: body and headers
    Request Body:
    {"recipients": ["+14155550123"], "message": "Healthchecks: $NAME missed its check-in"}
    
    Request Headers:
    x-api-key: YOUR_API_KEY
    Content-Type: application/json

Ready-made scenarios

  • The shop or website stops responding

    An HTTP monitor in Uptime Kuma checks the site every minute. After the failed retries you get a DOWN text, and UP once it recovers. Run Uptime Kuma on a different server from the shop, such as a small VPS.

  • Grafana: disk, 5xx errors, queue depth

    A Grafana alert rule (for example free disk under 10%) sends a text with the alert name and FIRING or RESOLVED. If you do not want a text on resolve, turn on Disable resolved message in the contact point.

  • The nightly backup never checked in

    Your backup script pings its Healthchecks.io URL after a successful run. When the ping is late, Healthchecks.io sends the SMS. You find out in the morning, not on the day you need the backup.

Errors and fixes

The test returns 401
Cause: No x-api-key header, a wrong key, or an Authorization header instead of x-api-key.
Fix: Check Additional Headers. If Uptime Kuma says Additional Headers is not a valid JSON, fix the quotes and commas.
The test returns 400
Cause: No Content-Type: application/json, so the API sees no fields, or no phone is enabled in the dashboard.
Fix: Add Content-Type to Additional Headers (in Healthchecks.io to Request Headers). Check in the dashboard that the phone is enabled.
The test works but an outage sends nothing
Cause: The notification is not ticked on the monitor.
Fix: Edit the monitor and tick the notification under Notifications, or use Apply on all existing monitors.
Grafana has no Custom Payload or Extra Headers
Cause: Grafana older than 12, or Grafana Cloud, where Custom Payload is not generally available yet.
Fix: Upgrade Grafana, or send the default webhook to n8n, take its title and build the SMS with an HTTP Request node.
The SMS arrives very late or not at all
Cause: The gateway phone was offline. Messages wait in the queue for up to 72 hours, then expire.
Fix: Keep the phone on a charger with battery optimisation off. Keep a second channel (email, Telegram) on the same monitor.

Frequently asked questions

Will the text arrive if my server or office internet is down?

Yes, as long as the monitoring tool runs elsewhere. The gateway phone is a separate device on mobile data, so a server or office line failure does not affect it. It still needs power and signal.

Does this replace a pager or on-call tool?

No. One phone is a single point of failure, with no escalation or acknowledgement. Treat SMS as one channel and add a second, such as email or Telegram.

How many texts will alerts use?

Each state change is one SMS per recipient (DOWN and UP make two). The Free plan has 300 SMS a month, Pro 3000. Set Retries so short blips do not eat the quota. The texts themselves are paid to your carrier through your SIM plan.

Can I alert several people?

Yes. Put several numbers in `recipients`, for example `["+14155550123", "+14155550124"]`. Each gets a separate SMS from your number.

Why does my text only fit 70 characters per part?

An emoji or a non-GSM character switches the encoding to UCS-2 and the limit drops from 160 to 70 characters per part. The template avoids emoji, but monitor names with such characters still switch the encoding.

Sources

  1. Uptime Kuma: Webhook notification source (GitHub)
  2. Grafana: Configure webhook notifications
  3. Healthchecks.io: Configuring Notifications
  4. Healthchecks.io: Webhook integration source (GitHub)
  5. smsportal API documentation

More in the guides

Send your first SMS today

Create a free account, connect your phone and see how customers respond to messages from your number.