Winter CMS plugin — reports application exceptions to a configurable HTTP webhook endpoint (JSON POST + Bearer token).
Lightweight, no external dependencies: ideal for hooking up a custom worker, n8n, Zapier, or any API that accepts JSON.
composer require quendistudio/monitoring
php artisan winter:upClone into plugins/quendistudio/monitoring, then:
php artisan winter:upBackend → Settings → Monitoring (QuendiStudio Plugins category). Full documentation is also available in the Documentation tab of the settings page.
Grant the quendistudio.monitoring.access_settings permission to the relevant administrators.
| Field | Description |
|---|---|
| Enable reporting | Master switch |
| Site identifier | Unique slug (e.g. mywebsite-www) — useful for multi-site setups |
| Site URL | Empty = APP_URL |
| Webhook URL | Public HTTP/HTTPS endpoint only (local/private addresses rejected) |
| Webhook secret | Key sent as Authorization: Bearer … |
| Deduplication (minutes) | Do not resend the same error before this interval |
| HTTP timeout | Max duration of the call (seconds) |
| Log lines to include | Tail excerpt from storage/logs/system*.log |
| Log file (optional) | Custom path inside storage/logs/ only |
| Verify SSL | Off = skip certificate check (local dev). On = always verify (production) |
| Report console errors | Includes CLI exceptions (artisan, cron) |
| Allow test route | Debug route (see Tests) |
Winter’s built-in $dontReport list (in Handler.php) tells the core not to log certain exception types to the application log — it does not mean they are unimportant. This plugin hooks exception.beforeReport, which runs before that filter, so the following are included when they reach the exception handler:
ApplicationException,ValidationException,AjaxException- HTTP, authentication, and model-not-found exceptions
- Any other
Throwablepassed toHandler::report()
Not reported: exceptions caught and handled before report() is called (expected control flow, successful validation redirects, etc.).
The JSON payload is designed for automated analysis. It may contain:
- Full exception messages and stack traces
- Request URL including query string (tokens, emails, etc.)
- Tail of
storage/logs/system*.log(may include secrets from log context)
Ensure the webhook endpoint and downstream services (agent, email provider) are trusted and secured. Restrict backend access to Monitoring settings.
Each exception triggers a POST to webhook_url with headers:
Content-Type: application/json
Authorization: Bearer {webhook_secret}
User-Agent: Quendistudio.Monitoring/1.0
| Field | Type | Description |
|---|---|---|
event |
string | Always winter.exception |
source |
string | Always quendistudio.monitoring |
site_id |
string | Site identifier (settings or hostname) |
site_url |
string | Site URL |
platform |
string | Always wintercms |
occurred_at |
string | ISO 8601 timestamp |
exception |
object | Exception details (see below) |
request |
object | HTTP context (empty in CLI) |
logs_tail |
string | Last lines of the system log file |
test |
bool | Present and true only for test payloads |
| Field | Type | Description |
|---|---|---|
class |
string | Exception FQCN |
message |
string | Error message |
file |
string | Source file |
line |
int | Source line |
code |
int | Error code |
trace |
string | Stack trace (truncated per settings) |
| Field | Type | Description |
|---|---|---|
url |
string | Full request URL |
method |
string | HTTP method |
ip |
string | Client IP address |
user_agent |
string | User-Agent (max 500 characters) |
{
"event": "winter.exception",
"source": "quendistudio.monitoring",
"site_id": "mywebsite-www",
"site_url": "https://www.example.com",
"platform": "wintercms",
"occurred_at": "2026-06-15T14:30:00+02:00",
"exception": {
"class": "Winter\\Storm\\Exception\\ApplicationException",
"message": "Something went wrong",
"file": "/var/www/plugins/vendor/plugin/SomeClass.php",
"line": 42,
"code": 0,
"trace": "#0 /var/www/index.php(42): ..."
},
"request": {
"url": "https://www.example.com/page",
"method": "GET",
"ip": "203.0.113.1",
"user_agent": "Mozilla/5.0 ..."
},
"logs_tail": "[2026-06-15 14:29:58] production.ERROR: ..."
}Same as above, plus "test": true and a simulated message.
The plugin sends a generic JSON format. Your endpoint (or an intermediary) is responsible for:
- validating the Bearer token;
- parsing the
exception,request, andlogs_tailfields; - triggering the desired action (email, ticket, centralized logging, etc.).
Integration examples:
- Serverless worker (Cloudflare Workers, AWS Lambda): receive the POST, format an email or alert.
- n8n / Zapier / Make: webhook trigger → map JSON fields to Slack, email, database.
- Internal API: store errors in your own monitoring system.
Extension plugins for native formats (Slack, Discord, Sentry, etc.) may be developed later; this plugin deliberately focuses on the generic webhook.
# Test JSON payload (without throwing an exception)
php artisan monitoring:testwebhookIntentional exception route (Development tab):
- Enable Allow test route
- Ensure
APP_DEBUG=true - Open
/quendistudio/monitoring/test-exception
Do not enable the test route in production.
Winter Handler::report()
→ exception.beforeReport (this plugin)
→ ExceptionPayloadBuilder (JSON)
→ ErrorReporter (deduplication after successful POST, async at end of request)
→ Your webhook
Cursor Automations can act as a webhook receiver with a Webhook trigger:
- Create an automation with a Webhook trigger.
- Copy the URL and authentication key into Webhook URL and Webhook secret.
- Configure the agent prompt to analyze the JSON payload (
exception,logs_tail,request). - Optional: chain a second webhook (email, Resend, Slack, etc.) from the automation.
The JSON schema documented above can be used directly by the Cursor agent — no transformation on the plugin side is required.
MIT — see LICENCE.md.