Clarification on Empty Unique ID for "Got Better" / "Got Worse" Alerts

I have configured LibreNMS to use Alertmanager as the alert transport for sending alerts to a webhook.
I’ve noticed that whenever an alert transitions to “got better” or “got worse,” the unique ID field appears empty. Could you confirm if this is the expected behavior, or if it might be a potential bug?

/opt/librenms $ ./validate.php

Component Version
LibreNMS 23.11.0 (2023-11-25T19:17:34+00:00)
DB Schema 2023_11_04_125846_packages_increase_name_column_length (273)
PHP 8.1.22
Python 3.10.13
Database MariaDB 10.5.24-MariaDB-1:10.5.24+maria~ubu2004
RRDTool 1.7.2
SNMP 5.9.3
===========================================

[OK] Installed from the official Docker image; no Composer required
[OK] Database connection successful
[OK] Database Schema is current
[OK] SQL Server meets minimum requirements
[OK] lower_case_table_names is enabled
[OK] MySQL engine is optimal
[OK] Database and column collations are correct
[OK] Database schema correct
[OK] MySQl and PHP time match
[OK] Active pollers found
[OK] Dispatcher Service is enabled
[OK] Locks are functional
[OK] No python wrapper pollers found
[OK] Redis is functional
[OK] rrd_dir is writable
[OK] rrdtool version ok
[WARN] Updates are managed through the official Docker image

Alert Template::::::::::::
Port HighUtilization---->

@php
// Custom query to fetch id from the alerts table based on device_id and timestamp
$customAlertId = DB::table(‘alerts’)
->where(‘device_id’, $alert->device_id)
->where(‘timestamp’, $alert->timestamp)
->value(‘id’);
@endphp

@if ($alert->state == 0)Time elapsed: {{ $alert->elapsed }} @endif
Timestamp: {{ $alert->timestamp }}
Unique-ID: {{ $customAlertId }}
@if ($alert->faults) Faults:
@foreach ($alert->faults as $key => $value)
#{{ $loop->iteration }}: class_name: “Port”
specific_Problem: “HighUtilization”
instanceName: {{ $value[‘hostname’] }}/{{ $value[‘ifIndex’] }}
hostName: {{ $value[‘hostname’] }}
ipAddress: {{ $value[‘ip’] }}
severity: {{ $alert->severity }}
description: “Port HighUtilization for {{ $value[‘hostname’] }}/{{ $value[‘ifName’] }}”
@endforeach
@endif

Alert Rule:::::::::::::
Other PortUtilization OverThreshold---->

macros.port_up = 1 AND ports.ifType != “ethernetCsmacd” AND devices.os = “junos” AND (macros.port_in_usage_perc >= 90 OR macros.port_out_usage_perc >= 90)

JSONs:
below are the alert jsons taken from alert manager:

{
“annotations”: {
“description”: “Timestamp: 2025-04-02 08:26:03\nUnique-ID: \n Faults:\n #1: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: brmnc01xxxxxxxxxxxxxxxxxx/800\n hostName: brmnc01xxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for brmnc01xxxxxxxxxxxxxxxxxx/irb.1052"\n #2: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: brmnc01xxxxxxxxxxxxxxxxxx/802\n hostName: brmnc01xxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for brmnc01xxxxxxxxxxxxx/irb.1054"\n”,
“summary”: “Other PortUtilization OverThreshold”,
“title”: “Alert for device brmnc01xxxxxxxxxxxxxx - Other PortUtilization OverThreshold got worse”
},
“endsAt”: “2025-04-03T08:28:28.309Z”,
“fingerprint”: “ba9b41c694334a64”,
“receivers”: [
{
“name”: “alertmanager”
}
],
“startsAt”: “2025-04-02T08:02:04.000Z”,
“status”: {
“inhibitedBy”: ,
“silencedBy”: ,
“state”: “active”
},
“updatedAt”: “2025-04-02T08:28:28.309Z”,
“generatorURL”: “http://localhost/device/822”,
“labels”: {
“alertname”: “Other PortUtilization OverThreshold”,
“instance”: “brmnc01xxxxxxxxxxxxxxxxx”,
“severity”: “warning”,
“source_dns”: “dyxinxxxxxxxxxxxxxxxx”,
“source_type”: “librenms”
}
},

{
“annotations”: {
“description”: “Timestamp: 2025-04-01 21:34:03\nUnique-ID: \n Faults:\n #1: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/690\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1009"\n #2: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/691\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1010"\n #3: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/692\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1011"\n #4: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/696\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1045"\n #5: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/697\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1061"\n #6: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/815\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1052"\n #7: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/817\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1054"\n #8: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/822\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1238"\n #9: class_name: "Port"\n specific_Problem: "HighUtilization"\n instanceName: sbknc01xxxxxxxxxxxxxxxxxxx/823\n hostName: sbknc01xxxxxxxxxxxxxxxxxxx\n ipAddress: 10.65.xx.xx\n severity: warning\n description: "Port HighUtilization for sbknc01xxxxxxxxxxxxxxxxxxx/irb.1239"\n”,
“summary”: “Other PortUtilization OverThreshold”,
“title”: “Alert for device sbknc01xxxxxxxxxxxxxxxxxxx - Other PortUtilization OverThreshold got better”
},
“endsAt”: “2025-04-02T21:36:53.129Z”,
“fingerprint”: “1bd62fa8b41df52b”,
“receivers”: [
{
“name”: “alertmanager”
}
],
“startsAt”: “2025-04-01T21:05:04.000Z”,
“status”: {
“inhibitedBy”: ,
“silencedBy”: ,
“state”: “active”
},
“updatedAt”: “2025-04-01T21:36:53.129Z”,
“generatorURL”: “http://localhost/device/402”,
“labels”: {
“alertname”: “Other PortUtilization OverThreshold”,
“instance”: “sbknc01xxxxxxxxxxxxxxxxxxx”,
“severity”: “warning”,
“source_dns”: “dyxinfxxxxxxxxxxxx”,
“source_type”: “librenms”
}
},

Issue was caused due to the condition used in the template (based on timestamp).
If I remove timestamp filter and use rule_id ($alert->rule_id) it is giving the id

@php
// Custom query to fetch id from the alerts table based on device_id and timestamp
$customAlertId = DB::table(‘alerts’)
->where(‘device_id’, $alert->device_id)
->where(‘timestamp’, $alert->timestamp)
->value(‘id’);
@endphp

May I know, what does $alert->timestamp field has? will it get updated in case of alert got worse/got better scenarios?

Why not just use $alert->id

Thanks for your response laf!!

Initially I was using $alert->id / $alert->uid, however when we try to ack the alert using this id via API we are getting an error saying “No Alert by that ID”
Hence, used customQuery to fetch alert id from alerts table in the template. Ack is working fine with this.

Snapshots from DB and JSON –

If I use $alert->id/$alert->uid, id is taken from alert_log table in the Output JSON,
with custom query, id is taken from alerts table in the output JSON.

JSON snapshot when $alert->id is used –

JSON snapshot when custom query is used –

API ack using $alert->id

Please suggest, why this behavior!!