WHM 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.

Why ntfy
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:
nano /var/cpanel/perl/Cpanel/iContact/Provider/Ntfy.pmAdd the following contents:
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:
nano /var/cpanel/perl/Cpanel/iContact/Provider/Schema/Ntfy.pmAdd the following contents:
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:
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.pmNotifications
Open Contact Manager.
WHM → Server Contacts → Contact ManagerConfigure:
- ntfy server URL
- Bearer token
- Topic names
Example:
NTFYHOST=https://ntfy.sh // or private self hosted instance
NTFYAUTHTOKEN=my-secret-token
CONTACTNTFY=whm,alerts,securityThis 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.
Author
Vincent VuVincent 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.
