Make unattended-upgrades call a webhook

On 2026-08-15, by mascal.

Introduction

unattended-upgrades (referenced as uu later) is really a neat tool to keep you Debian and derivatives up to date with security.

You can get notified of upgrades by mail, which is great if your server has mail capabilities, or find the whole mail ecosystem actually a good thing (I don't).

I've a webhook that is feeding a database and output the latest events as RSS. Instead of having of reading some local mail, I'm better off being notified through RSS on many possible devices.

uu proposes a Unattended-Upgrade::Post-Invoke-Success configuration option. After many headaches, I never managed to make it run, but the README.md mentionned a plugin system in python. We're going to use that.

Creating an outdated package

At first we need to be able to trigger an upgrade at will. The idea here is to create an outdated package with no other dependencies. On my servers, it appears that unzip is that, I just need a fake, inferior, version of it.

Installing equivs

sudo apt install equivs

Creating a fake package

Fill /tmp/fake_unzip with this:

Priority: optional
Standards-Version: 4.5.1

Package: unzip
Version: 0.0.1
Maintainer: Mister fake
Architecture: amd64
Description: fake unzip package
 This is a fake package. Upgrade me!

Let's build our deb package:

equivs-build /tmp/fake_unzip

Now you got a fake package at /tmp/unzip_0.0.1_amd64.deb. You can copy it in your server (we'll use /tmp again there)

Creating a plugin

Back to your server.

uu's use python for its plugin system. You'll need to install python3-requests to call the webhook URL.

You then need to create the plugin directory for uu:

sudo mkdir -p /etc/unattended-upgrades/plugins/
cd /etc/unattended-upgrades/plugins

The plugin

You can create /etc/unattended-upgrades/plugins/report.py, with the following code. Indeed you'll needed adjustments. Read carefully the comments, more stuff is available through the plugin, notably hostname (I don't need that):

#!/usr/bin/env python3

import sys

import requests

class UnattendedUpgradesPluginWebhook:
    """Call a webhook on unattended upgrade. Must start with
    UnattendedUpgradesPlugin*"""

    def post_webhook(self, title, description):
        try:
            r = requests.post("https://your_server/your_webhook", 
                data={"title": title, "description": description},
                timeout=30)
            r.raise_for_status()
        except Exception as e:
            print(e, file=sys.stderr)
            sys.exit(1)

    def postrun(self, result):
        # The data in result is a python class called PluginDataPostrun.
        # It can be viewed via "pydoc3 /usr/bin/unattended-upgrades"
        # and then searching for PluginDataPostrun.
        
        if len(result.packages_upgraded) == 0 and result.success:
            print("No packages were upgraded, don't send a notification")
            sys.exit()

        if not result.success:
            # Warning emoji
            title = "⚠️ Unattended-upgrades results: FAILURE"
            description = "<pre><code>" + result.log_dpkg + "</code></pre>"
            self.post_webhook(title, description)
            sys.exit(1)

        reboot = "[Reboot required] " if result.reboot_required else ""
        title = reboot + "Unattended-upgrades results: Success"
        description = f"""
            <p><b>Upgraded packages:</b>
                {", ".join(result.packages_upgraded)}
            </p>
            <pre><code>{result.log_dpkg}
            </code></pre>
        """
        self.post_webhook(title, description)

If you really need credentials in the code, don't forget to put proper permissions and ownership

Testing

You can now test by cycling deinstalls/unattended-upgrades, like this:

sudo apt -y remove unzip; sudo apt install /tmp/unzip_0.0.1_amd64.deb; sudo unattended-upgrade --debug -vvv

Have fun!