Black and white close-up of illuminated server racks in a data centre.

Push alerts for WHM using ntfy

Published 26 May, 2026

rely heavily on email for operational alerts. While functional, email notifications are often delayed, filtered, or ignored during incident response. Modern infrastructure teams increasingly prefer real-time push delivery for monitoring and administrative events.

This article demonstrates how to integrate self-hosted ntfy push notifications directly into WHM by creating a custom iContact notification provider. The implementation supports authenticated delivery, notification priorities, multiple topics, and native WHM configuration handling.


Problem

Default WHM notifications are email-centric. That introduces several operational limitations:

  • Delayed alert awareness
  • Dependence on mail infrastructure

For administrators managing multiple servers, immediate push delivery can significantly improve incident awareness.

ntfy web application interface.

Why ntfy

is a lightweight publish-subscribe notification service that supports:

  • Self-hosted deployments
  • Mobile push delivery
  • Authentication tokens
  • Priority levels
  • Topic-based routing
  • Minimal infrastructure overhead

Unlike traditional messaging integrations, ntfy can operate entirely within private infrastructure.


Requirements

Before starting, ensure the server has:

  • WHM/cPanel root access
  • Perl available
  • A running ntfy instance
  • Optional ntfy authentication token

Provider

Create the provider module:

bash
nano /var/cpanel/perl/Cpanel/iContact/Provider/Ntfy.pm

Add the following contents:

perl
package Cpanel::iContact::Provider::Ntfy;
 
use strict;
use warnings;
 
use parent 'Cpanel::iContact::Provider';
 
use Try::Tiny;
 
use Cpanel::Exception ();
use Cpanel::Config::LoadWwwAcctConf ();
use HTTP::Tiny;
use utf8 ();
 
sub send {
    my ($self) = @_;
 
    my $args_hr = $self->{'args'};
    my @errs;
 
    my $subject_copy = $args_hr->{'subject'};
 
    my $body_copy = ref $args_hr->{'text_body'}
        ? ${ $args_hr->{'text_body'} }
        : $args_hr->{'text_body'};
 
    my $subject = $subject_copy;
    my $body    = $body_copy;
 
    my $wwwacct_ref =
      Cpanel::Config::LoadWwwAcctConf::loadwwwacctconf();
 
    my $server =
      $wwwacct_ref->{'NTFYHOST'} // q{};
 
    my $token =
      $wwwacct_ref->{'NTFYAUTHTOKEN'} // q{};
 
    if ( !$server ) {
        die Cpanel::Exception::create(
            'MissingParameter',
            'The system could not send the ntfy notification because no ntfy host is configured.'
        );
    }
 
    my $importance =
         $args_hr->{'importance'}
      // $args_hr->{'level'}
      // 3;
 
    if ( $importance == 0 ) {
        return 1;
    }
 
    my $priority = 3;
    my $tags     = 'information';
 
    if ( $importance == 1 ) {
        $priority = 5;
        $tags     = 'rotating_light,warning';
 
    }
    elsif ( $importance == 2 ) {
        $priority = 4;
        $tags     = 'warning';
 
    }
    elsif ( $importance == 3 ) {
        $priority = 3;
        $tags     = 'information';
    }
 
    foreach my $topic ( @{ $args_hr->{'to'} } ) {
        my $safe_subject = $subject;
 
        utf8::decode($safe_subject);
 
        $safe_subject =~
          tr/\x{2018}\x{2019}\x{201C}\x{201D}/''""/;
 
        $safe_subject =~
          s/[^\x20-\x7E]//g;
 
        my $http = HTTP::Tiny->new(
            verify_SSL => 1,
            timeout    => 15,
        );
 
        try {
            my %headers = (
                'Title'        => $safe_subject,
                'Priority'     => $priority,
                'Tags'         => $tags,
                'Content-Type' => 'text/plain',
            );
 
            if ($token) {
                $headers{'Authorization'} =
                  "Bearer ${token}";
            }
 
            my $response = $http->post(
                "${server}/${topic}",
                {
                    headers => \%headers,
                    content => $body,
                }
            );
 
            if ( !$response->{success} ) {
                die Cpanel::Exception::create(
                    'ConnectionFailed',
                    'The system could not send data to ntfy due to an error: [_1]',
                    [
                        $response->{content}
                          || $response->{reason}
                    ]
                );
            }
 
        }
        catch {
            push @errs, $_;
        };
    }
 
    if (@errs) {
        die Cpanel::Exception::create(
            'Collection',
            [ exceptions => \@errs ]
        );
    }
 
    return 1;
}
 
1;



Schema

Create the schema module:

bash
nano /var/cpanel/perl/Cpanel/iContact/Provider/Schema/Ntfy.pm

Add the following contents:

perl
package Cpanel::iContact::Provider::Schema::Ntfy;
 
use strict;
use warnings;
 
use Cpanel::LoadModule ();
 
sub get_settings {
    return {
        'NTFYHOST' => {
            'shadow'   => 1,
            'type'     => 'text',
            'checkval' => sub {
                Cpanel::LoadModule::load_perl_module(
                    'Cpanel::StringFunc::Trim'
                );
 
                my $value = shift();
 
                $value =
                  Cpanel::StringFunc::Trim::ws_trim($value);
 
                return q{}
                  unless $value =~ m{^https?://.+$};
 
                return $value;
            },
            'label' =>
              'ntfy Server URL',
            'help' =>
              'Example: https://ntfy.example.com',
        },
        'NTFYAUTHTOKEN' => {
            'shadow'   => 1,
            'type'     => 'password',
            'checkval' => sub {
                Cpanel::LoadModule::load_perl_module(
                    'Cpanel::StringFunc::Trim'
                );
 
                my $value = shift();
 
                $value =
                  Cpanel::StringFunc::Trim::ws_trim($value);
 
                return $value;
            },
            'label' =>
              'ntfy Authentication Token',
            'help' =>
              'Bearer token for ntfy authentication.',
        },
        'CONTACTNTFY' => {
            'name'     => 'Ntfy',
            'shadow'   => 1,
            'type'     => 'text',
            'checkval' => sub {
 
                Cpanel::LoadModule::load_perl_module(
                    'Cpanel::StringFunc::Trim'
                );
 
                my $value = shift();
 
                $value =
                  Cpanel::StringFunc::Trim::ws_trim($value);
 
                return $value if $value eq q{};
 
                my @topics =
                  split m{\s*,\s*}, $value;
 
                return join(
                    ',',
                    grep(
                        m{^[a-zA-Z0-9._-]+$},
                        @topics
                    )
                );
            },
            'label' =>
              'ntfy Topics',
            'help' =>
              'Comma-separated ntfy topics. Example: whm,alerts,security',
        },
    };
}
 
sub get_config {
    return {
        'default_level'    => 'All',
        'display_name'     => 'ntfy',
        'icon_name'        => 'ntfy',
        'icon'             => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAPoAAAD6AG1e1JrAAACZUlEQVR4nI2TX0hTURzHrwbRYy89BY16LRAqwmJoOhBSqR6k10bvSdBLGWHJJFs6U7flP9ofN4aTNtf+iBDFlFnM2uY2s6kguJblrkvTebfdc/aNq1aubuUXvk/ndz7nnC/nyzAMwzQ2NhYDKGb2KGEWQJHYWtEeXahNQALAAsAPYALA679YWPODUmNscfHw7is9Xcnz6J30El3AB1PEj4GIH6boJMzv38L8IQBLLAjLbAjmuRDhAGQ2NjW7AS7z1BscuV+fO9vVRI+23KQlHXfpKW0TPd2toGf6H9BSnZJKje20RNeSe7G0AGSJm2GY7dwAOIdmAijTKAibXkf/5BiqdCpU6lpRberEBYsatdYnuPysFzKrmvhWEkAm6/yZBwDn8GwYJ9oaiNLrQXw1hYVUEtccBsgG2lEzqEXtUDcu2ftRMaQhvlQCyGZdBQDHXATHVXdIla4NI7EwBDW8dKBU/wgySxdkg2pU23ohs3UT39dPAPcbwD4bxjltM1nLcJhPLePGqBVV5k5cH7Xilvc5GsZcqH9lh8zeQyZWl/4AuIbnoyjve0gUXjcqje0oN6hQY9Ui9OUjcpSC5vMYTyyg0iEAPguAwgxs8xEcU93Onexp5qX6Vr7C9Jg/b+7gL9r6+Dqnnr/iNvJ1IwN8mb0n40uJPMHPJqB+N06m1pIIri0j+C2J0DqLwHpy2xssQpspRDOrRMgnEY8rtzZHo9H9lNLprdR4Oo0NzoI0p0eaM4hYn09zpqV4/J5cLj/04/QDLMte9Xg8UolEclD0r4tLfE5o2U7b/udfAAD7duos3rZ/6DtisCKyMNBu0wAAAABJRU5ErkJggg==',
    };
}
 
1;

Permissions

Set correct ownership and permissions:

bash
chmod 644 /var/cpanel/perl/Cpanel/iContact/Provider/Ntfy.pm
chmod 644 /var/cpanel/perl/Cpanel/iContact/Provider/Schema/Ntfy.pm
 
chown root:root /var/cpanel/perl/Cpanel/iContact/Provider/Ntfy.pm
chown root:root /var/cpanel/perl/Cpanel/iContact/Provider/Schema/Ntfy.pm

Notifications

Open Contact Manager.

text
WHM → Server Contacts → Contact Manager

Configure:

  • ntfy server URL
  • Bearer token
  • Topic names

Example:

text
NTFYHOST=https://ntfy.sh // or private self hosted instance
NTFYAUTHTOKEN=my-secret-token
CONTACTNTFY=whm,alerts,security

This integration adds native-style push notifications to WHM using ntfy and the iContact framework. The implementation is lightweight, self-hosted, and works without third-party SaaS notification providers.

For administrators managing infrastructure from mobile devices, ntfy provides significantly faster visibility than traditional email-only alerting.

Vincent is the founder and director of Rubix Studios, with over 20 years of experience in branding, marketing, film, photography, and web development. He is a certified partner with industry leaders including Google, Microsoft, and HubSpot. Vincent also serves as a member of the Maribyrnong City Council, Business and Innovation Board and is undertaking an Executive MBA at RMIT University.