A Laravel application that receives webhooks from Emby media server and displays them on a beautiful dashboard with metadata information and cover images fetched from TVDB and TMDB.
- πΌοΈ Media Dashboard: Beautiful grid layout showing your latest media additions with fully clickable cards
- π Detailed Metadata: Comprehensive information about movies, TV shows, and episodes
- π¨ Cover Images: Automatically fetches cover images from TVDB and TMDB
- β²οΈ Configurable Auto-refresh: Customizable timer for dashboard updates
- π Provider Integration: Support for TVDB and IMDB/TMDB metadata providers
- π± Responsive Design: Works perfectly on desktop and mobile devices
- β‘ Real-time Updates: Live webhook processing with instant dashboard updates
- π Interactive Cards: Click anywhere on a media card to view detailed information
- π Pagination: Paginated media grid with Flowbite-style navigation. The number of items per page is configurable.
- π΅ Pagination UI: Uses Flowbite's default pagination style with "<" and ">" for previous/next, blue highlight for the active page, and normal border.
- π¦ Smart Redirects: If a user visits a page with no data, they are redirected to page 1.
- ποΈ Advanced Filtering: Server-side filtering by media type with configurable item types and proper pagination reset.
- ποΈ Toggle Controls: Show/hide images and descriptions with cookie-based persistence across sessions.
- π Enhanced Metadata: Display media dimensions (width/height) in detailed view.
-
π§βπ» Clone the repository:
git clone https://github.com/krakerz/EmbyMedia-WebhookReceiver.git cd EmbyMedia-WebhookReceiver -
π¦ Install dependencies:
composer install npm install && npm run build -
βοΈ Set up environment:
cp .env.example .env php artisan key:generate
-
π Configure your
.envfile: (See Configuration Options below for all available settings) -
ποΈ Set up database:
php artisan migrate
-
π Start the server:
php artisan serve
For production deployment, here's a complete nginx configuration example:
server {
listen 80;
server_name your-domain.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
# Document root
root /var/www/emby-webhook/public;
index index.php index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# Main location block
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP-FPM configuration
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; # Adjust PHP version as needed
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
# Increase timeouts for webhook processing
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
}
# Webhook endpoint with specific configuration
location /emby/webhook {
# Allow larger request bodies for webhook payloads
client_max_body_size 10M;
# Rate limiting (adjust as needed)
limit_req zone=webhook burst=10 nodelay;
try_files $uri /index.php?$query_string;
}
# Static assets caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Deny access to sensitive files
location ~ /\. {
deny all;
}
location ~ /\.env {
deny all;
}
location ~ /storage/ {
deny all;
}
location ~ /bootstrap/cache/ {
deny all;
}
# Logs
access_log /var/log/nginx/emby-webhook.access.log;
error_log /var/log/nginx/emby-webhook.error.log;
}Add this to the http block in your main nginx configuration:
# Rate limiting for webhook endpoint
limit_req_zone $binary_remote_addr zone=webhook:10m rate=30r/m;-
Copy your application to the server:
sudo cp -r /path/to/EmbyMedia-WebhookReceiver /var/www/emby-webhook sudo chown -R www-data:www-data /var/www/emby-webhook sudo chmod -R 755 /var/www/emby-webhook sudo chmod -R 775 /var/www/emby-webhook/storage sudo chmod -R 775 /var/www/emby-webhook/bootstrap/cache
-
Enable the site:
sudo ln -s /etc/nginx/sites-available/emby-webhook /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx
-
Set up environment for production:
cd /var/www/emby-webhook sudo -u www-data cp .env.example .env sudo -u www-data php artisan key:generate sudo -u www-data php artisan migrate --force sudo -u www-data php artisan config:cache sudo -u www-data php artisan route:cache sudo -u www-data php artisan view:cache -
Configure environment variables:
APP_ENV=production APP_DEBUG=false APP_URL=https://your-domain.com # Database configuration DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=emby_webhook DB_USERNAME=your_db_user DB_PASSWORD=your_db_password # Other configurations...
- π₯ Firewall: Restrict access to the webhook endpoint to your Emby server IP
- π SSL: Always use HTTPS in production
- π¦ Rate Limiting: Implement rate limiting to prevent abuse
- π Monitoring: Set up log monitoring for the webhook endpoint
- πΎ Backup: Regular database backups of webhook data
- Configure your Emby server URL in
.envasEMBY_BASE_URL - Optionally add your Emby API key as
EMBY_API_KEYfor enhanced metadata - Images will be fetched directly from your Emby server using the format:
{{EMBY_BASE_URL}}/emby/Items/{{item_id}}/Images/Primary?tag={{image_tag}}&quality=90
- Visit TVDB API Information
- Create an account and request an API key
- Add your API key to
.envasTVDB_API_KEY
- Visit TMDB API Settings
- Create an account and request an API key
- Add your API key to
.envasIMDB_API_KEY
-
Access Emby Admin Dashboard:
- Dashboard β Plugins β Webhooks (install if not already installed)
- Or Dashboard β Notifications β Webhooks
-
Add a new webhook with:
- URL:
http://your-server-ip:8000/emby/webhook - Events: Select the events you want to track (recommended: Library events)
- Request content type:
application/json - Send all properties: Enabled
- URL:
-
Test the webhook:
- Add new media to your Emby library
- Check the dashboard for new entries
- π Visit the main page to see all webhook events
- π Click anywhere on any media card to see detailed information
- π The dashboard auto-refreshes based on your configured timer
- πΌοΈ Cover images are automatically fetched and cached
- ποΈ Use the filter buttons to show specific media types (Movies, TV Shows, Music)
- ποΈ Toggle Controls: Use the "Show Images" and "Show Descriptions" toggles to customize your view
- Show Images: When unchecked, images are blurred for a spoiler-free experience
- Show Descriptions: When unchecked, descriptions are hidden to reduce clutter
- Cookie Persistence: Your preferences are saved for 30 days and persist across browser sessions
- URL:
/emby/webhook - Method:
POST - Content-Type:
application/json - Authentication: π Uses
WEBHOOK_SECRETif set (see Security Best Practices)
The webhook endpoint returns:
{
"status": "success"
}You can control the visibility of the "Provider IDs" section in the media details view via the .env configuration.
Add the following variable to your .env file:
SHOW_PROVIDER_IDS=trueSet it to false to hide the Provider IDs section.
This setting is also available in the .env.example file for reference.
Configure the auto-refresh timer in your .env file:
WEBHOOK_REFRESH_TIMER=30 # Refresh every 30 secondsThe application fetches cover images in the following order:
- π Emby Server (primary source using item ID and image tags)
- πΊ TVDB (fallback for TV shows, seasons, episodes)
- π¬ TMDB (fallback using IMDB ID)
- π TMDB Search (final fallback using title and year)
Control whether raw webhook data is shown in the interface:
SHOW_RAW_WEBHOOK_DATA=true # Show raw data section (default)
SHOW_RAW_WEBHOOK_DATA=false # Hide raw data sectionControl which media types are shown and available for filtering on the dashboard:
# Comma-separated list of item types to show and filter
# Available types: Movie, Episode, Audio, Video, etc.
# Leave empty to show all types
WEBHOOK_ALLOWED_ITEM_TYPES="Movie,Episode,Audio"This configuration:
- Filters Display: Only shows media items of the specified types
- Dynamic Filter Buttons: Only creates filter buttons for allowed types
- Server-Side Filtering: Filtering is handled server-side with proper pagination
- Pagination Reset: When applying filters, pagination automatically resets to page 1
- URL Parameters: Filters are preserved in URLs and pagination links
Control which sections and features are visible in the webhook details and dashboard.
Set these in your .env file as needed:
SHOW_FILE_LOCATION=true # π Show file path section (default)
SHOW_WEBHOOK_EVENT_DETAILS=true # π‘ Show event details section (default)
SHOW_PROVIDER_IDS=true # π·οΈ Show Provider IDs section (default)
SHOW_PREMIERE_DATE=true # π
Show Premiere Date in media details (default)
WEBHOOKS_PAGINATION_PER_PAGE=12 # π’ Number of items per page in dashboard- Set any of these to
falseto hide the corresponding section. - Adjust
WEBHOOKS_PAGINATION_PER_PAGEto control dashboard pagination size.
Set the application timezone using the APP_TIMEZONE variable in your .env file. Use a valid PHP timezone identifier (e.g., Asia/Jakarta, UTC, America/New_York).
APP_TIMEZONE=Asia/JakartaThe database connection is always set to UTC internally for maximum compatibility. All date and time display and logic are handled by Laravel using the APP_TIMEZONE setting. You do not need to configure any other timezone variables.
Configure how long the "β¨ NEW" and "β¨ Recently Added" badges are shown on media cards (in minutes):
NEW_CARD_MINUTES=60This controls how long after creation a card is considered "new" or "recently added".
The application automatically creates clickable links from the ExternalUrls provided in the webhook response. These URLs are content-specific and provided directly by Emby:
- Dynamic URLs: Uses actual URLs from webhook
Item.ExternalUrlsarray - Content-Specific: URLs are tailored to the specific content (movie, episode, etc.)
- Multiple Providers: Supports any external provider that Emby has configured
- Provider IDs: Also displays provider IDs as reference data (non-clickable)
Example external URLs from webhook:
"ExternalUrls": [
{
"Name": "IMDb",
"Url": "https://www.imdb.com/title/tt37616507"
},
{
"Name": "TheTVDB",
"Url": "https://thetvdb.com/?tab=episode&id=11178926"
}
]All external links open in a new browser tab.
The application handles various Emby webhook events including:
library.new- New media added to libraryitem.added- Item added to libraryplayback.start- User started playbackplayback.stop- User stopped playbackuser.created- New user created- And many more...
EmbyMedia-WebhookReceiver/
βββ app/
β βββ Http/Controllers/
β β βββ EmbyWebhookController.php
β βββ Models/
β β βββ EmbyWebhook.php
β βββ Services/
β βββ TvdbService.php
β βββ ImdbService.php
β βββ EmbyService.php
β βββ ImageFetchingService.php
βββ database/migrations/
β βββ create_emby_webhooks_table.php
βββ resources/views/
β βββ webhooks/
βββ tests/Feature/
βββ ImageFetchingTest.php
βββ EmbyImageFetchingTest.php
The emby_webhooks table stores:
event_type- Type of webhook eventitem_type- Type of media (Movie, Episode, etc.)item_name- Name of the media itemitem_path- File path on serveruser_name- User who triggered the eventserver_name- Emby server namemetadata- Extracted metadata including cover imagesraw_payload- Complete webhook payload
Edit EmbyWebhookController::extractEventType() to handle additional event types.
Update EmbyWebhookController::extractMetadata() to extract additional fields.
Extend the ImageFetchingService to add support for additional image providers.
- Check Emby webhook configuration
- Verify the webhook URL is accessible
- Check Laravel logs:
storage/logs/laravel.log - Ensure proper firewall configuration
If you see an error like:
SQLSTATE[HY000]: General error: 1298 Unknown or incorrect time zone: 'Asia/Jakarta'
This means your MySQL server does not have timezone tables loaded.
- Verify API keys are correctly configured
- Check network connectivity to TVDB/TMDB
- Review application logs for API errors
- Ensure provider IDs exist in webhook data
- Consider caching API responses
- Implement image caching strategy
- Monitor API rate limits
- π Webhook Secret: Always set
WEBHOOK_SECRETin your.envto protect the/emby/webhookendpoint. If not set, the endpoint is open to anyone. - π HTTPS: Always use HTTPS for all endpoints, especially for webhooks and dashboard access.
- π‘οΈ IP Whitelisting: Restrict access to the webhook endpoint by IP (e.g., only allow your Emby server) using firewall or nginx rules.
- π¦ Rate Limiting: Consider adding rate limiting to the webhook endpoint to prevent abuse.
- π΅οΈ Sensitive Data in Logs: Avoid logging full webhook payloads in production, or mask sensitive fields in logs.
- π Dashboard Access: If your dashboard contains sensitive data, protect it with authentication middleware.
- ποΈ .env and Logs: Ensure
.env, storage, and log files are not accessible via the web server (see nginx config). - π‘οΈ Output Escaping: All dynamic content in Blade templates is escaped by default, but review custom HTML for possible XSS risks.
- π API Keys: Store all API keys and secrets in environment variables, never in source code or views.
- πΎ Backups: Regularly backup your webhook data and database.
Run the test suite:
php artisan testThe tests include:
- Webhook processing functionality
- Image fetching from TVDB and TMDB
- Timer configuration validation
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Submit a pull request
This repo created with Ai helps, fork it and modify it by yourself to make it suit for you.
This project is open-sourced software licensed under the MIT license.