The Scheduler Webhook plugin provides HTTP endpoints for Grav's Modern Scheduler, enabling webhook triggers and health monitoring for cloud environments, CI/CD pipelines, and external monitoring systems.
- 🔗 Webhook Triggers - Trigger scheduler via HTTP POST requests
- 💚 Health Monitoring - Check scheduler status via HTTP GET requests
- 🔒 Token Authentication - Secure webhook access with bearer tokens
- 🌐 CORS Support - Optional cross-origin request support
- ☁️ Cloud Ready - Perfect for serverless and containerized deployments
bin/gpm install scheduler-webhook- Download the plugin from GitHub
- Extract to
/user/plugins/scheduler-webhook - Run
bin/grav installto install dependencies
- Grav v1.7.0 or higher
- PHP 7.3.6 or higher
The scheduler's modern features are part of Grav core, so there is no separate "modern scheduler" toggle to turn on. You just need to enable the webhook trigger and set a token.
Admin Panel:
- Navigate to Configuration → Scheduler
- Click on "Modern Features" tab
- Enable the webhook trigger and set a token
- Save configuration
Manual Configuration:
Edit user/config/scheduler.yaml:
modern:
webhook:
enabled: true
token: 'your-secure-token-here' # Generate with: openssl rand -hex 32
health:
enabled: trueThe plugin is enabled by default once installed. You can configure it via:
Admin Panel: Configuration → Plugins → Scheduler Webhook
Manual Configuration:
Edit user/config/plugins/scheduler-webhook.yaml:
enabled: true
cors: false # Set to true if you need cross-origin requestsURL: /scheduler/webhook
Method: POST
Authentication: token (optional but recommended)
The token can be sent three ways, checked in this order:
X-Webhook-Tokenheader (recommended) —X-Webhook-Token: your-secure-token-hereAuthorizationheader —Authorization: Bearer your-secure-token-here(server-dependent — many Apache + PHP-FPM/FastCGI hosts strip this header before it reaches PHP; see the note below)tokenquery parameter —?token=your-secure-token-here
The header is
X-Webhook-Token— notX-API-Token, which belongs to the separate Grav API plugin. Using the wrong header name is a common cause of a401 Invalid authorization token.
Why
X-Webhook-Tokenis recommended: many Apache + PHP-FPM/FastCGI setups strip theAuthorizationheader before it reaches PHP, which makes Bearer tokens fail with a 401. Custom headers likeX-Webhook-Tokenare never stripped, so they work everywhere without server changes. If you prefer Bearer, see Authentication Failures for the.htaccessrule that forwards the header.
curl -X POST https://your-site.com/scheduler/webhook \
-H "X-Webhook-Token: your-secure-token-here"Response:
{
"success": true,
"message": "Scheduler executed",
"jobs_run": 3,
"timestamp": "2025-01-24T10:30:00+00:00"
}When you specify a job ID, the webhook will force-run that job immediately, regardless of its schedule. This is useful for manual triggers or CI/CD pipelines.
curl -X POST https://your-site.com/scheduler/webhook?job=backup \
-H "X-Webhook-Token: your-secure-token-here"Response:
{
"success": true,
"message": "Job force-executed successfully",
"job_id": "backup",
"forced": true,
"output": "Backup completed successfully"
}Note: This is a manual override - the job runs immediately even if it's not scheduled to run at this time.
URL: /scheduler/health
Method: GET
Authentication: None required (configurable)
curl https://your-site.com/scheduler/healthResponse:
{
"status": "healthy",
"last_run": "2025-01-24T10:30:00+00:00",
"last_run_age": 120,
"queue_size": 0,
"failed_jobs_24h": 0,
"scheduled_jobs": 3,
"modern_features": true,
"webhook_enabled": true,
"health_check_enabled": true,
"timestamp": "2025-01-24T10:32:00+00:00"
}Status values:
healthy- Last run within 10 minuteswarning- Last run within 1 hourcritical- Last run over 1 hour agounknown- No run history found
.github/workflows/scheduler.yml:
name: Trigger Grav Scheduler
on:
schedule:
- cron: '*/5 * * * *' # Every 5 minutes
workflow_dispatch: # Manual trigger
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Trigger Scheduler
run: |
curl -X POST ${{ secrets.SITE_URL }}/scheduler/webhook \
-H "X-Webhook-Token: ${{ secrets.SCHEDULER_TOKEN }}" \
-f # Fail on HTTP errors
- name: Check Health
run: |
curl ${{ secrets.SITE_URL }}/scheduler/health \
-f # Fail on HTTP errors.gitlab-ci.yml:
trigger-scheduler:
stage: deploy
script:
- |
curl -X POST ${SITE_URL}/scheduler/webhook \
-H "X-Webhook-Token: ${SCHEDULER_TOKEN}" \
--fail
only:
- schedulesimport json
import urllib3
def lambda_handler(event, context):
http = urllib3.PoolManager()
response = http.request(
'POST',
'https://your-site.com/scheduler/webhook',
headers={
'X-Webhook-Token': SCHEDULER_TOKEN
}
)
return {
'statusCode': response.status,
'body': response.data.decode('utf-8')
}apiVersion: batch/v1
kind: CronJob
metadata:
name: grav-scheduler
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: scheduler-trigger
image: curlimages/curl:latest
args:
- /bin/sh
- -c
- |
curl -X POST https://your-site.com/scheduler/webhook \
-H "X-Webhook-Token: ${SCHEDULER_TOKEN}" \
--fail
restartPolicy: OnFailureAdd a new monitor:
- Monitor Type: HTTP(s)
- URL:
https://your-site.com/scheduler/health - Method: GET
- Expected Status Code: 200
- Heartbeat Interval: 60 seconds
Create a new uptime check:
- Check type: HTTP
- URL:
https://your-site.com/scheduler/health - Check for string:
"status":"healthy"
Generate a secure token:
# Linux/Mac
openssl rand -hex 32
# Or use PHP
php -r "echo bin2hex(random_bytes(32));"- Always use HTTPS in production
- Use strong tokens (at least 32 characters)
- Rotate tokens periodically
- Restrict access via firewall rules if possible
- Monitor access logs for suspicious activity
- Use rate limiting with a reverse proxy
http {
limit_req_zone $binary_remote_addr zone=scheduler:10m rate=1r/m;
server {
location /scheduler/webhook {
limit_req zone=scheduler burst=5;
proxy_pass http://localhost:8080;
}
}
}- Ensure the plugin is enabled
- Check that Modern Scheduler is enabled in configuration
- Clear Grav cache:
bin/grav cache
If you get 401 Invalid authorization token:
-
Verify the token matches exactly (no extra spaces)
-
Check the token is properly configured in
scheduler.yaml -
Confirm the header name is
X-Webhook-Token, notX-API-Token— the latter belongs to the Grav API plugin and this endpoint ignores it -
If you are using
Authorization: Bearerand it always 401s, your server is most likely stripping theAuthorizationheader (common on Apache with PHP-FPM/FastCGI). Either switch to theX-Webhook-Tokenheader, which is never stripped, or forward the header to PHP by adding this to your Grav.htaccess, just afterRewriteEngine On:RewriteCond %{HTTP:Authorization} . RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
- Check cron/systemd timer is configured
- Verify scheduler is running:
bin/grav scheduler --run - Check logs:
tail -f logs/grav.log
Enable CORS in plugin configuration:
# user/config/plugins/scheduler-webhook.yaml
enabled: true
cors: trueUse ngrok for local testing:
# Install ngrok
brew install ngrok # Mac
# or download from https://ngrok.com
# Expose local site
ngrok http 8080
# Use the ngrok URL for testing
curl -X POST https://abc123.ngrok.io/scheduler/webhook \
-H "X-Webhook-Token: test-token"Enable Grav debugging for detailed logs:
# user/config/system.yaml
debugger:
enabled: true
errors:
display: 1
log: true
log:
handler: file
syslog:
facility: local6- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Discord: Grav Discord
MIT License - see LICENSE file for details
Developed by Trilby Media for the Grav CMS community.