A lightweight Node.js server that receives webhook posts from Unifi Protect Alarm Manager and forwards them to a Discord webhook. The entire application is containerized using Docker for easy deployment.
- π¨ Real-time Alerts: Receive and forward Unifi Protect events to Discord
- π Security: Rate limiting, request validation, and data sanitization
- π Structured Logging: Comprehensive logging with request tracing
- π³ Docker Ready: Complete containerization with health checks
- β‘ Lightweight: Optimized for minimal resource usage
- π§ Configurable: Environment-based configuration
The latest Docker image is automatically published to GitHub Container Registry on each release.
# Pull the latest image
docker pull ghcr.io/btholt/discord-unifi:latest
# Run with environment variables
docker run -d \
--name unifi-discord-bridge \
-p 3000:3000 \
-e DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/YOUR_WEBHOOK_URL_HERE" \
ghcr.io/btholt/discord-unifi:latest- Docker and Docker Compose
- Discord webhook URL
- Unifi Protect system with webhook capability
git clone <repository-url>
cd unifi-discord-bridge
cp env.example .envEdit .env file with your Discord webhook URL:
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL_HEREdocker-compose up -dThe service will be available at http://localhost:3000
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
DISCORD_WEBHOOK_URL |
Required | Discord webhook URL |
WEBHOOK_PATH |
/webhook/unifi |
Endpoint path for Unifi webhooks |
LOG_LEVEL |
info |
Logging level (error, warn, info, debug) |
WEBHOOK_SECRET |
- | Optional secret for webhook validation |
RATE_LIMIT_WINDOW |
15 |
Rate limiting window in minutes |
RATE_LIMIT_MAX |
100 |
Max requests per window |
PROTECT_API_KEY |
- | Unifi Protect API key for thumbnail fetching |
PROTECT_HOST |
192.168.1.80 |
Unifi Protect host address |
- Go to your Discord server settings
- Navigate to Integrations β Webhooks
- Create a new webhook
- Copy the webhook URL
- Set it as
DISCORD_WEBHOOK_URLin your environment
- POST
/webhook/unifi- Receive Unifi Protect webhooks - GET
/webhook/unifi/health- Health check endpoint
- GET
/- Service information
The service expects JSON payloads from Unifi Protect with the following structure:
{
"alarm": {
"name": "Motion Detected",
"sources": [],
"conditions": [
{
"condition": {
"type": "is",
"source": "motion"
}
}
],
"triggers": [
{
"key": "motion",
"device": "74ACB99F4E24"
}
]
},
"timestamp": 1722526793954
}The service automatically detects event types from the alarm.conditions[].condition.source field:
motion- Motion detection eventsalert- General alertsperson- Person detectionvehicle- Vehicle detectionpackage- Package detectionface_known- Known person face recognitionface_unknown- Unknown person face recognitionunknown- Fallback for unrecognized event types
When PROTECT_API_KEY is configured, the service automatically:
- Fetches Animated Thumbnails: Downloads GIF thumbnails from Unifi Protect
- Uploads to Discord: Attaches thumbnails to Discord messages
- Fallback Gracefully: Continues without thumbnails if API key is missing
# Add to your .env file
PROTECT_API_KEY=your_protect_api_key_here
PROTECT_HOST=192.168.1.80 # Optional, defaults to 192.168.1.80The service fetches thumbnails using:
http://{PROTECT_HOST}/proxy/protect/api/events/{eventId}/animated-thumbnail?keyFrameOnly=true&speedup=10
Events are transformed into rich Discord embeds with:
- Event-specific emojis and colors
- Camera information
- Timestamps
- Structured fields
Example Discord message:
π¨ **Motion Detected**
Unifi Protect Alert
Motion detected on camera: Front Door
Camera: Front Door | Event Type: Motion Detection
# Install dependencies
npm install
# Start development server
npm run dev# Build image
docker build -t unifi-discord-bridge .
# Run container
docker run -p 3000:3000 -e DISCORD_WEBHOOK_URL=your_url unifi-discord-bridgeThe application uses structured logging with Winston. Log levels:
error- Application errorswarn- Warning messagesinfo- General informationdebug- Detailed debugging information
Each request gets a unique request ID for tracing.
- Rate Limiting: Prevents abuse with configurable limits
- Request Validation: Validates incoming webhook data
- Data Sanitization: Removes sensitive information
- Helmet: Security headers
- CORS: Cross-origin resource sharing protection
- Non-root User: Docker container runs as non-root user
The application includes health check endpoints for container orchestration:
curl http://localhost:3000/webhook/unifi/healthResponse:
{
"status": "healthy",
"timestamp": "2025-01-15T10:30:00.000Z",
"service": "unifi-discord-bridge",
"version": "1.0.0"
}-
Discord webhook not working
- Verify the webhook URL is correct
- Check Discord server permissions
- Ensure the webhook is not disabled
-
Rate limiting errors
- Adjust
RATE_LIMIT_MAXandRATE_LIMIT_WINDOWvalues - Check if multiple instances are running
- Adjust
-
Container health check failing
- Verify the service is running on the correct port
- Check container logs for errors
View application logs:
# Docker Compose
docker-compose logs -f
# Docker container
docker logs unifi-discord-bridgeThis project uses GitHub Actions for automated CI/CD:
When you create a new release tag (e.g., v1.0.1), the workflow will:
- Build Docker Image - Multi-platform build (AMD64, ARM64)
- Push to GHCR - Publish to GitHub Container Registry
- Create Release - Generate release notes with usage instructions
- Security Scan - Run vulnerability scanning with Trivy
# Create and push a new tag
git tag v1.0.1
git push origin v1.0.1Or use the GitHub UI to create a release.
- Pull Requests: Build and test on every PR
- Main Branch: Build and push to GHCR on every push
- Security: Automated vulnerability scanning
- Testing: Docker image validation
# Pull the latest release
docker pull ghcr.io/btholt/discord-unifi:latest
# Run in production
docker run -d \
--name unifi-discord-bridge \
--restart unless-stopped \
-p 3000:3000 \
-e DISCORD_WEBHOOK_URL="your_discord_webhook_url" \
-e LOG_LEVEL="info" \
ghcr.io/btholt/discord-unifi:latest-
Environment Setup
cp env.example .env # Edit .env with production values -
Docker Compose
docker-compose -f docker-compose.yml up -d
-
Reverse Proxy (Optional) Configure nginx or similar to proxy requests to the container.
For production deployments, consider setting resource limits:
services:
unifi-discord-bridge:
deploy:
resources:
limits:
memory: 256M
cpus: "0.5"- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
MIT License - see LICENSE file for details.
For issues and questions:
- Check the troubleshooting section
- Review application logs
- Open an issue on GitHub