ConnectWise Automate Alerts — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ConnectWise Automate Alerts (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Alerts in ConnectWise Automate are notifications generated by monitors, scripts, or system events that require attention. This skill covers alert listing, acknowledgment, history tracking, and ticket creation workflows.
assignment are monitor configuration; use connectwise-automate-monitors.
it; the ticket's board, status, priority and SLA clock live in ConnectWise PSA. Use connectwise-psa-tickets.
the word; use datto-rmm-alerts, ninjaone-alerts, atera-alerts or auvik-alerts.
| Source | Description | Example |
|---|---|---|
| Monitor | Generated by monitor threshold | CPU > 90% |
| Script | Generated by script execution | Backup failed |
| Event Log | Windows Event Log trigger | Security event |
| System | Automate system events | Agent offline |
| Manual | User-created alerts | Maintenance note |
| Level | Value | Description | Response Time |
|---|---|---|---|
Information | 1 | Informational only | Review at convenience |
Warning | 2 | Potential issue | Investigate within hours |
Error | 3 | Failure detected | Respond within SLA |
Critical | 4 | Severe/emergency | Immediate response |
Generated → Active → Acknowledged → Resolved
│ │
│ └── Ticket Created
│
└── Auto-Cleared (if condition clears)| Status | Description |
|---|---|
New | Just generated, unread |
Active | Open, unacknowledged |
Acknowledged | Someone is working on it |
Resolved | Issue fixed, alert closed |
Cleared | Condition auto-cleared |
Suppressed | Temporarily hidden |
See references/fields.md for the complete Alert and AlertHistory field reference.
Alerts are managed through /cwa/api/v1/Alerts endpoints using a SQL-like condition query parameter for filtering (not standard REST query params):
GET /cwa/api/v1/Alerts?condition=Status in ('New','Active')&pageSize=100
Authorization: Bearer {token}Action endpoints (Acknowledge, Resolve, Suppress, CreateTicket) are POST requests to /Alerts/{alertID}/{Action} and most require a Notes field in the body. A bulk acknowledgment endpoint exists at /Alerts/BulkAcknowledge for handling related alerts together.
See references/api.md for the complete endpoint catalog with request/response examples (list, filter by client/severity, acknowledge, resolve, add note, create ticket, get history, suppress, bulk acknowledge).
async function getCriticalAlertsDashboard(client) {
const criticalAlerts = await client.request(
`/Alerts?condition=Severity >= 3 and Status in ('New','Active')&pageSize=100`
);
const dashboard = {
totalCritical: criticalAlerts.length,
byClient: {},
byCategory: {},
oldest: null
};
for (const alert of criticalAlerts) {
// Group by client
const clientName = alert.ClientName || 'Unknown';
if (!dashboard.byClient[clientName]) {
dashboard.byClient[clientName] = [];
}
dashboard.byClient[clientName].push({
id: alert.AlertID,
subject: alert.Subject,
computer: alert.ComputerName,
severity: alert.Severity,
age: getAlertAge(alert.TimeGenerated)
});
// Group by category
const category = alert.Category || 'Uncategorized';
dashboard.byCategory[category] = (dashboard.byCategory[category] || 0) + 1;
// Track oldest
if (!dashboard.oldest || new Date(alert.TimeGenerated) < new Date(dashboard.oldest.TimeGenerated)) {
dashboard.oldest = alert;
}
}
return dashboard;
}
function getAlertAge(timeGenerated) {
const now = new Date();
const generated = new Date(timeGenerated);
const diffMs = now - generated;
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 60) return `${diffMins} minutes`;
if (diffMins < 1440) return `${Math.floor(diffMins / 60)} hours`;
return `${Math.floor(diffMins / 1440)} days`;
}async function acknowledgeAndCreateTicket(client, alertId, options = {}) {
const {
notes = 'Acknowledged and ticket created',
priority = 2,
boardId = 1
} = options;
// Get alert details
const alert = await client.request(`/Alerts/${alertId}`);
// Acknowledge the alert
await client.request(`/Alerts/${alertId}/Acknowledge`, {
method: 'POST',
body: JSON.stringify({ Notes: notes })
});
// Create ticket
const ticketResponse = await client.request(`/Alerts/${alertId}/CreateTicket`, {
method: 'POST',
body: JSON.stringify({
TicketSubject: alert.Subject,
Priority: alert.Severity >= 3 ? 1 : priority,
BoardID: boardId,
Notes: `Auto-created from Automate alert\n\n${alert.Message}`
})
});
return {
alert: {
id: alertId,
subject: alert.Subject,
status: 'Acknowledged'
},
ticket: {
id: ticketResponse.TicketID,
number: ticketResponse.TicketNumber
}
};
}async function triageAlertsByClient(client, clientId) {
const alerts = await client.request(
`/Alerts?condition=ClientID = ${clientId} and Status in ('New','Active')&pageSize=200`
);
const triage = {
client: clientId,
total: alerts.length,
critical: [],
error: [],
warning: [],
info: []
};
for (const alert of alerts) {
const summary = {
id: alert.AlertID,
subject: alert.Subject,
computer: alert.ComputerName,
source: alert.SourceName,
age: getAlertAge(alert.TimeGenerated)
};
switch (alert.Severity) {
case 4: triage.critical.push(summary); break;
case 3: triage.error.push(summary); break;
case 2: triage.warning.push(summary); break;
default: triage.info.push(summary);
}
}
return triage;
}async function bulkResolveAlerts(client, alertIds, notes) {
const results = [];
for (const alertId of alertIds) {
try {
await client.request(`/Alerts/${alertId}/Resolve`, {
method: 'POST',
body: JSON.stringify({ Notes: notes })
});
results.push({ alertId, status: 'resolved' });
} catch (error) {
results.push({ alertId, status: 'failed', error: error.message });
}
// Respect rate limits
await sleep(100);
}
return {
resolved: results.filter(r => r.status === 'resolved').length,
failed: results.filter(r => r.status === 'failed').length,
details: results
};
}async function checkAlertEscalation(client) {
const alerts = await client.request(
`/Alerts?condition=Status = 'Active' and Severity >= 2&pageSize=500`
);
const escalations = [];
const now = new Date();
for (const alert of alerts) {
const generated = new Date(alert.TimeGenerated);
const ageMinutes = (now - generated) / 60000;
// Escalation rules based on severity and age
let shouldEscalate = false;
let reason = '';
switch (alert.Severity) {
case 4: // Critical
if (ageMinutes > 15) {
shouldEscalate = true;
reason = 'Critical alert unacknowledged for 15+ minutes';
}
break;
case 3: // Error
if (ageMinutes > 60) {
shouldEscalate = true;
reason = 'Error alert unacknowledged for 1+ hour';
}
break;
case 2: // Warning
if (ageMinutes > 240) {
shouldEscalate = true;
reason = 'Warning alert unacknowledged for 4+ hours';
}
break;
}
if (shouldEscalate) {
escalations.push({
alertId: alert.AlertID,
subject: alert.Subject,
client: alert.ClientName,
computer: alert.ComputerName,
severity: alert.Severity,
ageMinutes: Math.round(ageMinutes),
reason
});
}
}
return escalations;
}| Error | Status | Cause | Resolution |
|---|---|---|---|
| Alert not found | 404 | Invalid AlertID | Verify alert exists |
| Already resolved | 400 | Alert already closed | Check current status |
| Permission denied | 403 | No access to alert | Check user permissions |
| Invalid status | 400 | Invalid status transition | Follow lifecycle rules |
| Ticket creation failed | 400 | PSA integration error | Check ticket board config |
See references/examples.md for a sample error response, a safe-resolve helper that checks status before resolving, and a full multi-step alert response workflow template.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.