Gareth Wilson Gareth Wilson

Guide to Datto RMM Webhooks: Features and Best Practices

Published


Datto RMM webhooks send monitor alerts from Datto's remote monitoring and management platform to your own systems. When an alert is raised on a managed device, and optionally when it resolves, Datto RMM can send a notification you shape yourself, so you can open tickets in a PSA Datto doesn't integrate with, post to an on-call tool, or keep your own alert history.

This guide covers how Global Webhooks are set up, how to authenticate their requests with a secret header, how the alert templates and variables work, and the practices that make a Datto RMM receiver dependable.

What are Datto RMM webhooks?

Datto RMM can send a webhook from a single monitor, or from every monitor in the account through Global Webhooks. This guide covers Global Webhooks only. Turning them on disables the webhooks set on individual monitors, and per-monitor webhooks have no way to carry a secret header, so Global Webhooks are the option you can authenticate.

The body is a template you write. Datto RMM replaces [variable] placeholders with alert, device and site values, and you can write a second template for when the alert resolves. Datto RMM adds no event field of its own, so the template decides everything your endpoint sees.

Datto RMM doesn't sign webhooks. The way to tell a real request from a forged one is a header you add with Add Header and check on your side.

Datto RMM webhook features

FeatureDetails
ConfigurationSetup > Integrations > Global Webhooks
PermissionsAdministrator
ScopeAlerts of the priorities you select, from all monitors
URLUp to 500 characters
Content Typeapplication/json (default), text/plain or application/x-www-form-urlencoded
BodyYour alert raised template, plus an optional alert resolved template
Custom headersAdd Header, name and value, with an option to hide the value
Maximum payload64 KB
Signature and timestampNone
TestTest Alert Webhook, raised or resolved
HistorySent messages appear on the alert's Timeline card
Method, retries, timeout, source IPsNot documented

Alerts, templates and variables

Global Webhooks send two kinds of notification:

  • Alert raised, sent when a monitor raises an alert of a priority you selected.
  • Alert resolved, sent only if you fill in the resolved template. It goes to the same URL, and only when the alert resolves automatically. Resolving an alert by hand sends nothing.

Because there is no event field, write one into each template. This is an illustration, not a vendor schema:

{
  "status": "raised",
  "alert_uid": "[alert_uid]",
  "alert_type": "[alert_type]",
  "alert_category": "[alert_category]",
  "alert_message": "[alert_message]",
  "device_hostname": "[device_hostname]",
  "site_name": "[site_name]"
}

Use "status": "resolved" in the resolved template, and keep [alert_uid] in both so you can match a resolution to the alert it closes.

The variables Datto documents include:

GroupVariables
Alert[alert_uid], [alert_type], [alert_category], [alert], [alert_message] (also _en, _de, _fr)
Device[device_id], [device_uid], [device_hostname] (or [hostname]), [device_os], [device_description], [device_ip], [device_udf1] to [device_udf30], [last_user]
Site[site_id], [site_uid], [site_name] (or [sitename])
Other[platform]

[device_ip] is the device's public IP address, not its LAN address. Datto's docs don't say whether substituted values are escaped for JSON, so an alert message containing a quote could break the body. Test with real alerts and handle a body that doesn't parse.

Inspect Datto RMM webhooks as they arrive. Point the Global Webhook at a Hookdeck Inspect URL and click Test Alert Webhook to see exactly what your template renders, with no account or setup required.

Setting up Datto RMM webhooks

Choose a header name and a long random value first, for example Authorization and Bearer followed by the output of openssl rand -hex 32.

  1. Sign in as an Administrator and go to Setup > Integrations > Global Webhooks. Turning these on disables your per-monitor webhooks, so plan for anything that depends on them.
  2. Select the alert priorities to send.
  3. Enter your HTTPS endpoint as the URL.
  4. Keep Content Type at application/json unless your receiver needs something else.
  5. Click Add Header, enter your header's name and value, and hide the value.
  6. Write the alert raised template, and the alert resolved template if you want resolutions.
  7. Save, then click Test Alert Webhook for both raised and resolved.

To route through Hookdeck, create a source with the Datto RMM type and use its URL as the webhook URL. Enter the header's name in API Key Header and its exact value, including any Bearer prefix, in API Key. The source accepts POST. Datto doesn't document which method Global Webhooks use, so send a test alert and check that it arrives before you rely on it.

Securing Datto RMM webhooks

Read the header you added, case-insensitively, and compare its whole value with the one you stored, in constant time. Check the header before parsing the body, and don't assume JSON: the content type is whatever you set on the webhook.

const crypto = require('crypto');
const express = require('express');

const HEADER = (process.env.DATTO_RMM_WEBHOOK_HEADER || 'Authorization').toLowerCase();
const SECRET = process.env.DATTO_RMM_WEBHOOK_SECRET; // the exact, whole header value

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/datto-rmm', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get(HEADER);
  if (!SECRET || typeof received !== 'string' || !safeEqual(received, SECRET)) {
    return res.sendStatus(401);
  }

  const raw = req.body.toString('utf8');
  let alert = raw;
  if (req.is('application/json')) {
    try {
      alert = JSON.parse(raw);
    } catch {
      return res.sendStatus(400); // usually a template or escaping problem
    }
  }

  // With the template above: alert.status is "raised" or "resolved".
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os

HEADER = os.environ.get("DATTO_RMM_WEBHOOK_HEADER", "Authorization")
SECRET = os.environ.get("DATTO_RMM_WEBHOOK_SECRET", "")


def verify_datto_rmm_request(headers) -> bool:
    if not SECRET:
        return False  # fail closed when unconfigured
    received = headers.get(HEADER)  # Starlette and Flask headers are case-insensitive
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), SECRET.encode("utf-8"))

A static header authenticates the sender, not the body. It can't detect a modified payload, and with no signed timestamp a captured request can be replayed and still pass. Serve the endpoint over HTTPS only, use a long random value, and replace it in Datto RMM and in your receiver if it might have leaked.

Make Datto RMM alerts production-ready. Hookdeck Event Gateway checks your secret header on every Datto RMM alert, queues each one durably, and lets you replay any alert after a fix.

Datto RMM webhook limitations and pain points

No signature, and per-monitor webhooks can't be authenticated

The Problem: Anyone who learns the header value can send convincing alerts, and alerts from per-monitor webhooks arrive with nothing to check.

Why It Happens: Datto RMM computes nothing over the body, and only Global Webhooks let you add a header.

Workarounds:

  • Use Global Webhooks with a long random header value, over HTTPS only.
  • Before acting on an alert, such as running a remediation job, confirm it in Datto RMM.

How Hookdeck Can Help: Hookdeck's Datto RMM source compares the header you name with the exact value you enter. Hookdeck answers Datto RMM with a 200 either way, but a request with a missing or wrong value is recorded as failed verification and never reaches your destination.

Manual resolutions send nothing

The Problem: An alert stays open in your ticketing system after a technician has closed it in Datto RMM.

Why It Happens: The resolved payload is only sent when an alert auto-resolves.

Workarounds:

  • Reconcile your open tickets against Datto RMM's open alerts on a schedule.
  • Let technicians close the downstream ticket directly when they resolve by hand.

How Hookdeck Can Help: Hookdeck can't create an event Datto doesn't send, but its request log shows which alerts did receive a resolution, which narrows the reconciliation.

Delivery behaviour is undocumented

The Problem: You can't tell whether an alert sent during your outage will arrive later.

Why It Happens: Datto's docs describe the Timeline card but not a timeout or retry policy.

Workarounds:

  • Respond with 200 as soon as the header checks out, and do the work afterwards.
  • After an outage, check the Timeline card of affected alerts.

How Hookdeck Can Help: Hookdeck accepts each alert as soon as it arrives, stores it, and delivers it to your service with retries you configure, so a short outage on your side doesn't depend on Datto retrying.

Template mistakes break the body

The Problem: One alert produces JSON your parser rejects, or a template edit renames a field.

Why It Happens: You own the template, Datto doesn't validate it, and the docs don't say whether values are escaped.

Workarounds:

  • Keep the templates in version control and change them alongside your receiver.
  • Return 400 for an unparseable body and log the raw request.

How Hookdeck Can Help: Hookdeck stores every request with its raw body and headers, and Hookdeck transformations can normalise the body before delivery. Once the template is fixed, replay the failed alerts.

Best practices

Store the full header value

Keep the exact value, prefix included, as one string and compare it whole. Put the name and value in configuration, not code, so you can rotate them.

Write a status field and keep the alert UID

Put your own status in each template, and include [alert_uid] in both, so the receiver can route and match resolutions to alerts.

Deduplicate on the alert UID and status

Use [alert_uid] plus your status field as an idempotency key, and store it before acting. See our guide to webhook idempotency.

Stay under 64 KB

Long alert messages and many user-defined fields add up. Send identifiers and the fields you act on, rather than every variable.

Acknowledge fast and process asynchronously

Check the header, store the alert, return 200, and create tickets afterwards. See why to process webhooks asynchronously.

Conclusion

Datto RMM Global Webhooks send each alert, and each automatic resolution, as a body built from your own template, authenticated only by a header you add. Use Global Webhooks rather than per-monitor ones, compare the whole header value in constant time, write a status field and the alert UID into both templates, and reconcile manual resolutions separately.

Hookdeck Event Gateway sits in front of your receiver, checks the header, and holds every alert until your service has processed it.

Get started with Hookdeck for free and handle Datto RMM webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.