Notifications Setup
Get real-time alerts for backup failures, restore completions, and scheduled job issues via 100+ notification services.
Supported Services
Borg Web UI uses Apprise for notifications, which supports:
- Email (Gmail, Outlook, Yahoo, custom SMTP)
- Messaging (Slack, Discord, Telegram, Microsoft Teams, Matrix)
- Push Notifications (Pushover, Pushbullet, ntfy)
- SMS (Twilio, AWS SNS, Nexmo)
- Custom Webhooks (JSON, XML)
- And 100+ more services
Full list: Apprise Supported Notifications
Quick Start
- Navigate to Settings > Notifications tab
- Click Add Service
- Enter service details:
- Name: Friendly identifier (e.g., “Gmail Alerts”, “Slack - DevOps”)
- Service URL: Apprise URL format for your service
- Title Prefix: Optional prefix for notification titles (e.g., “[Production]”)
- Event Triggers: Select which events should trigger notifications
- Click Test to verify the configuration
- Click Add to save
Service URL Examples
Email (Gmail)
Requirements:
- Gmail account with 2-Step Verification enabled
- App Password generated (instructions)
URL Format:
mailto://username:app_password@gmail.com?smtp=smtp.gmail.com&mode=starttls
Example:
mailto://john:abcdwxyzpqrs@gmail.com?smtp=smtp.gmail.com&mode=starttls
Slack
Requirements:
- Slack Incoming Webhook URL (create one)
URL Format:
slack://TokenA/TokenB/TokenC/
Example:
slack://T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX/
Discord
Requirements:
- Discord Webhook URL from channel settings
URL Format:
discord://webhook_id/webhook_token
Example:
discord://123456789012345678/abcdef-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Telegram
Requirements:
- Bot token from @BotFather
- Chat ID (send a message to your bot, then get chat_id from
https://api.telegram.org/bot<token>/getUpdates)
URL Format:
tgram://bot_token/chat_id
Example:
tgram://123456789:ABCdefGHIjklMNOpqrsTUVwxyz/987654321
Microsoft Teams
Requirements:
- Teams Incoming Webhook URL (create one)
URL Format:
msteams://TokenA/TokenB/TokenC/
Pushover
Requirements:
- Pushover user key and application token (get them here)
URL Format:
pover://user_key@app_token
Example:
pover://uQiRzpo4DXghDmr9QzzfQu27cmVRsG@azGDORePK8gMaC0QOYAMyEEuzJnyUi
ntfy
Requirements:
- Topic name (public or self-hosted ntfy server)
URL Format:
ntfy://topic_name
Example:
ntfy://my-backup-alerts
Custom Webhook (JSON)
Send structured notifications to custom endpoints for automation and monitoring.
URL Format:
jsons://hostname/path/to/endpoint # HTTPS (secure)
json://hostname/path/to/endpoint # HTTP
Examples:
jsons://webhook.site/abc-123-def-456
jsons://myserver.com/api/webhooks/backup-alerts
json://localhost:8080/notifications
❌ Common Mistakes:
json://https://webhook.site/abc-123 ← WRONG (double protocol)
https://webhook.site/abc-123 ← Won't work with Apprise
✅ Correct:
jsons://webhook.site/abc-123 ← Use jsons:// for HTTPS webhooks
Payload Structure:
{
"version": "1.0",
"title": "✅ Backup Successful",
"message": "Archive: backup-2026-01-30\n...",
"type": "success"
}
See Notification Enhancements for details on JSON data extraction and automation.
Notification Events
Configure which events trigger notifications for each service:
Backup Events
- Backup Success - Sent when a manual or scheduled backup completes successfully
- Includes: Archive name, repository, file statistics, completion time
- Recommended: Disable for frequent backups to avoid notification fatigue
- Backup Failure - Sent when a backup fails
- Includes: Repository name, error details, job ID
- Recommended: Always enable for critical repositories
Restore Events
- Restore Success - Sent when a restore operation completes
- Includes: Archive name, repository, destination path
- Restore Failure - Sent when a restore operation fails
- Includes: Archive name, repository, error details
Schedule Events
- Schedule Failure - Sent when a scheduled backup job fails
- Includes: Schedule name, repository, error details
- Recommended: Always enable to catch missed backups
Notification Message Format
Success Notifications
Example backup success notification:
Title: [Production] ✅ Backup Successful (if title prefix is “[Production]”)
Body:
Archive: manual-backup-2025-11-23T18:28:30
Repository: /local/backups/important-data
Statistics:
• Original size: 3.94 GB
• Compressed size: 3.94 GB
• Deduplicated size: 245.82 MB
✓ Completed at 2025-11-23 18:28:35 UTC
Failure Notifications
Example backup failure notification:
Title: [Production] ❌ Backup Failed
Body:
Repository: /local/backups/important-data
Error Details:
Repository does not exist at /local/backups/important-data
⚠ Failed at 2025-11-23 19:15:42 UTC
Title Prefixes
Add a custom prefix to all notification titles from a service to:
- Distinguish between environments (e.g., “[Production]”, “[Staging]”, “[Dev]”)
- Identify the source system (e.g., “[Main Server]”, “[Backup NAS]”)
- Categorize notifications (e.g., “[Critical]”, “[Info]”)
Examples:
[Production]→ “[Production] ✅ Backup Successful”[NAS]→ “[NAS] ❌ Backup Failed”[Dev Server]→ “[Dev Server] ✅ Restore Successful”
Testing Notifications
Always test your notification configuration before relying on it:
- Click the Test button (flask icon) next to the notification service
- Check your configured service for the test message
- Verify the notification appears correctly
Test message format:
Title: 🔔 Borg UI Test Notification
Body: This is a test notification from Borg Web UI.
If you received this, your notification service is configured correctly!
Troubleshooting
Gmail Notifications Not Sending
Error: “SMTP AUTH extension not supported by server”
Solution: Add mode=starttls to the URL:
mailto://user:app_password@gmail.com?smtp=smtp.gmail.com&mode=starttls
Error: “Failed to send test notification”
Common causes:
- App Password incorrect - App Passwords are 16 characters without spaces
- 2-Step Verification not enabled - Required for App Passwords
- Wrong Gmail account - Ensure you’re using the correct account
- Network/firewall issues - Check that port 587 is accessible
To generate Gmail App Password:
- Go to Google Account Security
- Enable 2-Step Verification if not already enabled
- Go to App Passwords
- Generate a new App Password for “Mail”
- Copy the 16-character password (spaces are ignored)
Slack Notifications Not Sending
Error: “Invalid service URL format”
Solution: Verify your webhook URL structure. You can extract tokens from a Slack webhook URL:
Slack webhook: https://hooks.slack.com/services/T00000000/B00000000/XXXX
Apprise URL: slack://T00000000/B00000000/XXXX/
Discord Notifications Not Sending
Solution: Ensure you copied the complete webhook URL from Discord:
- Go to Discord channel settings > Integrations > Webhooks
- Copy the webhook URL (should be long, ~120 characters)
- Extract the webhook ID and token from the URL
- Format:
discord://webhook_id/webhook_token
Notification Service Shows “Last Used: Never”
This means notifications haven’t been triggered yet. This is normal for new services or if configured events haven’t occurred.
To verify it works:
- Click the Test button to send a test notification
- Trigger a backup manually to test success/failure notifications
Notification Enhancements
Include Job Name in Title
When enabled, notification titles include the job or schedule name for easier identification:
Without job name:
✅ Backup Successful
With job name:
✅ Backup Successful - Daily Backup
This helps when:
- Managing multiple scheduled backups
- Filtering notifications in chat services
- Quickly identifying which backup succeeded/failed
To enable:
- Edit a notification service
- Check “Include job/schedule name in title”
- Save
JSON Data for Automation
Get structured JSON data in notifications for monitoring tools, automation scripts, and log aggregation.
✨ How It Works:
Use json:// or jsons:// URLs to automatically receive pure JSON data - no checkbox needed!
jsons://webhook.site/your-id ← Secure HTTPS
json://myserver.com:8080/webhook ← HTTP
When you use these URL formats, the notification message body contains pure JSON string (compact format):
{"event_type":"backup_success","timestamp":"2026-01-30T12:26:46.986552","repository_name":"/backups/my-repo","archive_name":"backup-2026-01-30","job_name":"Daily Backup","stats":{"original_size":4011450946,"compressed_size":3723854484,"deduplicated_size":0},"completed_at":"2026-01-30T12:30:15.123456"}
Other service types (Slack, Discord, email, etc.) receive human-readable notifications without JSON data.
JSON Data Structure
Different events include different fields:
backup_success / backup_start / backup_failure:
{
"event_type": "backup_success",
"timestamp": "2026-01-30T12:00:00",
"repository_name": "my-repo",
"repository_path": "/backups/my-repo",
"archive_name": "backup-2026-01-30",
"job_name": "Daily Backup",
"source_directories": ["/data", "/home"],
"stats": {
"original_size": 1073741824,
"compressed_size": 536870912,
"deduplicated_size": 268435456
},
"completed_at": "2026-01-30T12:05:00"
}
restore_success / restore_failure:
{
"event_type": "restore_success",
"timestamp": "2026-01-30T12:00:00",
"repository_name": "my-repo",
"repository_path": "/backups/my-repo",
"archive_name": "backup-2026-01-30",
"job_name": null,
"target_path": "/restore/destination",
"completed_at": "2026-01-30T12:15:00"
}
schedule_failure:
{
"event_type": "schedule_failure",
"timestamp": "2026-01-30T12:00:00",
"schedule_name": "Daily Backup",
"repository_name": "my-repo",
"repository_path": "/backups/my-repo",
"error_message": "Connection timeout",
"failed_at": "2026-01-30T12:00:30"
}
Using JSON Webhooks
Correct URL Format:
For secure webhooks (HTTPS):
jsons://webhook.site/your-unique-id
jsons://myserver.com/api/webhooks/backup-alerts
For HTTP webhooks:
json://myserver.com:8080/webhooks
❌ Common Mistake:
json://https://webhook.site/abc123 ← WRONG! (double protocol)
✅ Correct:
jsons://webhook.site/abc123 ← Use jsons:// for HTTPS
Webhook Payload Structure
When using json:// or jsons://, Apprise sends:
{
"version": "1.0",
"title": "✅ Backup Successful - Daily Backup",
"message": "{\"event_type\":\"backup_success\",\"timestamp\":\"2026-01-30T12:00:00\",\"repository_name\":\"my-repo\",\"archive_name\":\"backup-2026-01-30\",\"job_name\":\"Daily Backup\",\"stats\":{\"original_size\":1073741824,\"compressed_size\":536870912}}",
"attachments": [],
"type": "success"
}
Fields:
version- Apprise protocol version (always “1.0”)title- Notification title (includes job name if enabled)message- Pure JSON string (when usingjson://orjsons://) OR formatted notification body (for other services)type- Event severity:info,success,warning,failureattachments- Always empty array (reserved for future use)
Parsing JSON Webhooks
TIP: When using JSON webhooks (json:// or jsons://), the message field contains pure JSON string - just use JSON.parse(payload.message) or json.loads(payload['message']). No regex extraction needed!
Python Example:
import json
# For JSON webhooks (json:// or jsons://)
def handle_json_webhook(webhook_payload):
"""Parse JSON webhook - message field contains pure JSON."""
message = webhook_payload.get('message', '')
# Simple JSON parse - no regex needed!
backup_data = json.loads(message)
print(f"Event: {backup_data['event_type']}")
print(f"Repository: {backup_data['repository_name']}")
print(f"Archive: {backup_data['archive_name']}")
if 'stats' in backup_data:
original_gb = backup_data['stats']['original_size'] / (1024**3)
print(f"Size: {original_gb:.2f} GB")
return backup_data
# Example usage
webhook_data = {
"title": "✅ Backup Successful - Daily Backup",
"message": '{"event_type":"backup_success","timestamp":"2026-01-30T12:00:00","repository_name":"my-repo","archive_name":"backup-2026-01-30","stats":{"original_size":1073741824}}',
"type": "success"
}
backup_data = handle_json_webhook(webhook_data)
Node.js Example:
// For JSON webhooks (json:// or jsons://)
function handleJsonWebhook(webhookPayload) {
const message = webhookPayload.message || '';
// Simple JSON parse - no regex needed!
const backupData = JSON.parse(message);
console.log(`Event: ${backupData.event_type}`);
console.log(`Repository: ${backupData.repository_name}`);
console.log(`Archive: ${backupData.archive_name}`);
if (backupData.stats) {
const originalGB = backupData.stats.original_size / (1024**3);
console.log(`Size: ${originalGB.toFixed(2)} GB`);
}
return backupData;
}
// Example usage
const webhookData = {
title: "✅ Backup Successful - Daily Backup",
message: '{"event_type":"backup_success","timestamp":"2026-01-30T12:00:00","repository_name":"my-repo","archive_name":"backup-2026-01-30","stats":{"original_size":1073741824}}',
type: "success"
};
const backupData = handleJsonWebhook(webhookData);
Bash/jq Example:
#!/bin/bash
# Parse JSON webhook POST request (for json:// or jsons:// URLs)
# Assuming webhook payload is in $1
# For JSON webhooks, message field contains pure JSON - no extraction needed!
EVENT_TYPE=$(echo "$1" | jq -r '.message | fromjson | .event_type')
REPO=$(echo "$1" | jq -r '.message | fromjson | .repository_name')
ARCHIVE=$(echo "$1" | jq -r '.message | fromjson | .archive_name')
ORIGINAL_SIZE=$(echo "$1" | jq -r '.message | fromjson | .stats.original_size')
echo "Event: $EVENT_TYPE"
echo "Repository: $REPO"
echo "Archive: $ARCHIVE"
echo "Size: $(($ORIGINAL_SIZE / 1024 / 1024 / 1024)) GB"
# Or parse message once and reuse:
BACKUP_DATA=$(echo "$1" | jq -r '.message | fromjson')
echo "Full data: $BACKUP_DATA"
Service-Specific JSON Formatting
For JSON Webhooks (json:// or jsons://):
- The
messagefield contains pure JSON string (compact, no markdown) - Simple to parse:
JSON.parse(payload.message)in JavaScript,json.loads(payload['message'])in Python - Optimized for automation and monitoring tools
For Other Services (Email, Slack, Discord, etc.):
- The
messagefield contains formatted notification body with embedded JSON in markdown code blocks - JSON appears as collapsible
<details>in email, or code blocks in chat - Human-readable with pretty-printed JSON (indented)
- Automation tools need regex extraction (see examples for
https://webhooks below)
Extracting JSON from Non-JSON Webhooks
If you’re using regular webhooks (https://, form://, etc.) instead of JSON webhooks, the JSON is embedded in markdown:
Python Example (for https:// webhooks):
import re
import json
def extract_json_from_markdown(webhook_payload):
"""Extract JSON from markdown code block in message."""
message = webhook_payload.get('message', '')
# Find JSON code block in markdown
match = re.search(r'```json\n(.*?)\n```', message, re.DOTALL)
if match:
return json.loads(match[1])
return None
backup_data = extract_json_from_markdown(webhook_payload)
Why Two Formats?
- JSON Webhooks - Optimized for automation (compact JSON, easy parsing)
- Other Services - Optimized for humans (formatted notifications with pretty-printed JSON)
Testing JSON Webhooks
Quick Test Setup:
- Go to webhook.site
- Copy your unique URL (e.g.,
https://webhook.site/abc-123) - In Borg UI → Settings → Notifications → Add Service:
Name: JSON Test URL: jsons://webhook.site/abc-123 ✅ Enable notifications ✅ Include job/schedule name in title ✅ Include JSON data in message body ✅ Notify on: Backup Success - Click Test or run a backup
- Check webhook.site to see the full payload
What You’ll See:
{
"version": "1.0",
"title": "🚀 Backup Started - My Backup",
"message": "**Archive:** backup-2026-01-30...\n\n**📊 JSON Data (for automation)**\n```json\n{\"event_type\": \"backup_start\", ...}\n```",
"type": "info"
}
Use Cases
1. Prometheus/Grafana Monitoring: Extract metrics from backup stats:
stats = backup_data.get('stats', {})
prometheus_metrics = f"""
backup_original_size_bytes{{repo="{repo}"}} {stats['original_size']}
backup_compressed_size_bytes{{repo="{repo}"}} {stats['compressed_size']}
backup_deduplicated_size_bytes{{repo="{repo}"}} {stats['deduplicated_size']}
"""
2. Log Aggregation (ELK, Splunk): Forward structured events to centralized logging:
import logging
logger.info("Backup completed", extra=backup_data)
3. Alerting Rules: Implement custom alert logic:
if backup_data['event_type'] == 'backup_failure':
if 'lock' in backup_data['error_message'].lower():
send_page_to_oncall("Backup locked - manual intervention needed")
4. Backup Reporting: Generate daily/weekly backup reports:
daily_backups.append({
'time': backup_data['completed_at'],
'repo': backup_data['repository_name'],
'size_gb': backup_data['stats']['original_size'] / (1024**3)
})
Best Practices
-
Test Before Relying - Always send a test notification before depending on alerts
-
Enable Failure Notifications - At minimum, enable backup and schedule failure notifications
-
Disable Success for Frequent Backups - If you backup hourly, success notifications create noise
-
Use Multiple Services - Configure backup notifications to email AND Slack for redundancy
-
Set Title Prefixes - Distinguish notifications from different systems
-
Monitor “Last Used” - Check the “Last Used” timestamp periodically to ensure notifications are working
-
Secure Service URLs - Notification URLs contain credentials. Keep them secure.
-
Test After Updates - Re-test notifications after updating Borg Web UI
Security Considerations
- Service URLs contain credentials - Store them securely, don’t share publicly
- Database encryption - Service URLs are stored in the database; secure the
/datavolume - Access controls - Only admins can configure notifications
- HTTPS in production - Use HTTPS/reverse proxy to protect the web interface
- Webhook authentication - Use authenticated webhooks when possible (e.g., Discord, Slack)
Advanced Configuration
Multiple Notification Services
You can add multiple notification services for different purposes:
Example setup:
- Gmail - Critical alerts only (backup failures, schedule failures)
- Slack - All events for team visibility
- Pushover - Mobile notifications for urgent issues
Per-Repository Notifications
Currently, notifications are global for all repositories. To achieve per-repository notifications:
- Create multiple notification services with descriptive names
- Use title prefixes to identify the source
- Manually enable/disable services based on needs
Future enhancement: Per-repository notification configuration is planned.
Need Help?
- Full Apprise Documentation: Apprise Wiki
- Service-Specific Guides: Apprise Notifications
- GitHub Issues: Report problems
- GitHub Discussions: Ask questions