image-forge.net

Webhooks: RMM, PSA & chat integration

Push every capture and deployment to another system the moment it finishes.

ImageForge can POST a signed JSON message to a URL you choose whenever a capture or deployment completes. That covers an RMM or PSA inbound webhook, a Slack/Teams/Discord channel, or any endpoint you write yourself.

Pick the right payload format. Chat services validate the body against their own schema and reject anything else. Slack answers HTTP 400 invalid_payload if you send it the raw event. Choosing the matching format in the console is what makes those integrations work — see payload formats below.

Set it up

  1. Get an inbound webhook URL from the receiving system (see per-service notes).
  2. In the Forge console, open activity and find the webhook (RMM / PSA integration) panel.
  3. Paste the URL, tick enabled, and choose the payload format that matches the receiver.
  4. Click send test event. A message appears in the receiving system within a second or two, and the console reports the exact HTTP status it got back.
  5. Click apply to save.
  6. Click regenerate signing secret and copy the secret immediately — it is shown once. Configure it on the receiving end if that system verifies signatures.
Test before you save. The test button uses whatever URL and format are currently in the form, not the saved values, so you can prove a receiver works before committing to it. Test sends never appear in the recent-deliveries table — that table only records real events.

Payload formats

FormatUse it forBody sent
raw (default)RMM, PSA, and receivers you write yourself The event object itself — see payload reference
slackSlack incoming webhooks, and services that emulate them {"text": "…"}
teamsMicrosoft Teams incoming webhooks A MessageCard, colour-coded green for success and red for failure
discordDiscord channel webhooks {"content": "…"}

The chat formats render one readable line, with the failure reason on a second line when there is one:

Deploy succeeded - win11-golden on FRONT-01 (Latitude 7090) by JB [4m12s]
Deploy failed - win11-golden on FRONT-01 (Latitude 7090) by JB [1m38s]
DISM apply failed: exit status 112

Payload reference (raw format)

The raw body is the same object the audit trail stores. Optional fields are omitted when empty, so treat anything except id, time, type, and result as possibly absent.

{
  "id": "9f2c1b7a",
  "time": "2026-08-13T14:22:05Z",
  "type": "deploy",
  "result": "success",
  "technician": "JB",
  "serial": "ABC123",
  "model": "Latitude 7090",
  "mac": "00:11:22:33:44:55",
  "hostname": "FRONT-01",
  "image_id": "3f8a…",
  "image_name": "win11-golden",
  "profile": "front office",
  "duration_s": 252,
  "message": ""
}
FieldNotes
typecapture or deploy
resultsuccess or failure
technicianStamped by the Forge from the authenticated identity, not supplied by the PC
hostnameThe name assigned by the deployment profile, when one applied
messageThe failure reason on a failure; usually empty on success
duration_sWhole seconds

Delivery behaviour

DetailValue
MethodPOST, Content-Type: application/json
User agentImageForge-Webhook/1
Fires onCompletion of a capture or a deployment — nothing else
SuccessAny 2xx response
Retries3 attempts total, ~1s then ~2s apart, 5-second timeout each
OrderingNot guaranteed; deliveries are independent

Delivery is best-effort and deliberately cannot affect imaging: the audit trail is the durable record, and a webhook is a notification on top of it. A receiver that is down, slow, or rejecting messages never delays or fails a capture or deployment.

Verifying the signature

Every delivery carries:

X-ImageForge-Signature: sha256=<hex>

That is an HMAC-SHA256 of the exact raw request body, keyed with your signing secret. Compute it over the bytes as received — do not re-serialise the JSON first, since any change in key order or whitespace produces a different result. The header is only sent when a secret exists.

// Node.js
const crypto = require("crypto");
function valid(rawBody, header, secret) {
  const want = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(header || "");
  const b = Buffer.from(want);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
# PowerShell
$hmac = [System.Security.Cryptography.HMACSHA256]::new([Text.Encoding]::UTF8.GetBytes($secret))
$hash = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($rawBody))
$want = "sha256=" + (($hash | ForEach-Object { $_.ToString("x2") }) -join "")

Regenerating the secret invalidates the old one immediately, so update the receiver in the same maintenance window.

Per-service notes

RMM / PSA (ConnectWise, Datto, NinjaOne, Atera, and similar)

Use the raw format. Create an inbound webhook or automation trigger that accepts JSON, then map the fields above onto whatever the platform records. Most systems key on serial to match an existing asset, and treat result: "failure" as the condition worth alerting on. If the platform requires its own authentication header rather than a signature, put that token in the URL's query string where supported — ImageForge redacts query strings from its own logs.

Slack

Create an incoming webhook in your Slack app configuration and use the slack format. The raw format will fail every delivery with invalid_payload.

Microsoft Teams

Use the teams format with an incoming-webhook connector URL, or a Workflows ("Post to a channel when a webhook request is received") URL. ImageForge sends a MessageCard, which both accept.

Discord

Channel settings → Integrations → Webhooks → copy the webhook URL, and use the discord format.

When events are not arriving

SymptomLikely cause
Test send reports invalid_payload or a 400 Wrong payload format for the service. Pick the matching one.
Test send times out or cannot connect The Forge cannot reach the URL — outbound firewall, proxy, or an internal-only hostname. The Forge makes this call, not the PC being imaged.
401/403 from the receiver The receiving system wants its own auth token, separate from the ImageForge signature.
Test works, real events never arrive Check enabled is ticked and the settings were saved with apply. Events only fire on completed captures and deployments.
Receiver rejects the signature The HMAC must be computed over the raw body bytes, not a re-serialised copy, and the secret must match the current one.

The panel's recent-deliveries table shows the last 20 real deliveries with their status codes, which is the first place to look. It is kept in memory, so it resets when the Forge service restarts.

Only one URL. A Forge sends to a single endpoint. To fan out to several systems, point ImageForge at one receiver that re-broadcasts — most RMM and automation platforms can do this, as can a few lines of script.