Sending results to a webhook
Your RMM gets Pardon's exit code, and usually its stdout. When you want the full answer somewhere else, such as survey answers in a spreadsheet, pings on a dashboard, or reboot deferrals in your PSA, Pardon can also POST the result to a URL you run.
Turning it on
Add the URL on the command line:
$env:PARDON_WEBHOOK_SECRET = '<your shared secret>'
pardon.exe show --payload .\wifi-check.json --webhook-url https://hooks.example.com/pardon
or in the campaign itself:
"report": {
"webhook": { "url": "https://hooks.example.com/pardon", "retries": 5, "timeout_seconds": 15 }
}
Pardon sends the same result/v1 JSON it prints on stdout, as the body of a POST with Content-Type: application/json. Use an https:// URL: the body can contain usernames and survey answers.
Order and failures
Every run delivers its result in this order:
- stdout,
- the result file, if you passed
--result-file, - the webhook.
The webhook comes last on purpose. If your endpoint is down, the RMM still gets its exit code and output. A failed webhook writes a line to stderr and never changes the exit code.
Pardon tries again on connection errors, timeouts, 408, 429 and 5xx responses, waiting 1, 2, 4 seconds and so on between attempts. Other 4xx responses are not retried, because sending the same thing again won't help. The defaults are 3 retries and a 10-second timeout per attempt; retries and timeout_seconds change them.
--result-file is the only way to get a result file; a report.result_file inside a campaign is ignored.
Keep the secret out of the campaign
With a secret, Pardon signs every POST so your endpoint can tell it really came from Pardon. Where the secret comes from, in order of preference:
--webhook-secret <secret>on the command line,- the
PARDON_WEBHOOK_SECRETenvironment variable, report.webhook.hmac_secretin the campaign.
The first two win over the campaign. Prefer the environment variable or your RMM's secure-variable feature: a campaign file gets copied around, and a hosted campaign is readable by anyone who can fetch it.
When Pardon runs as SYSTEM and fans out to signed-in users, only the SYSTEM process posts, so you get one combined result per machine, never one per user. The webhook settings and secret are removed from the copies started in each user's session and from the reprompt copy in C:\ProgramData\Pardon\campaigns\ (which users can read). Pardon keeps them in the locked state folder instead (state\<campaign_id>.report.json, SYSTEM and administrators only), so reprompts still post.
The campaign handed to each user's session goes on its command line, so it is capped at 30,000 characters. A larger one fails with exit 11 and a message naming the limit. Even a 20-question survey is far below that.
Checking the signature
Each request carries two headers:
| Header | Value |
|---|---|
X-Pardon-Timestamp | Unix time in seconds when the request was signed |
X-Pardon-Signature | sha256= followed by the lowercase hex HMAC-SHA256 of "{timestamp}.{body}", using your secret |
X-Pardon-Timestamp is always sent; X-Pardon-Signature only when there is a secret. report.webhook.hmac_header renames the signature header if your receiver expects a different one.
To check a request, rebuild the signed string from the timestamp header, a dot, and the exact body bytes you received (before any JSON parsing). Compare the signatures in constant time, and reject timestamps more than five minutes from your own clock, so a captured request can't be replayed later. In Python:
import hashlib, hmac, time
def verify(secret: bytes, headers, body: bytes, max_skew=300) -> bool:
ts = headers.get("X-Pardon-Timestamp", "")
sig = headers.get("X-Pardon-Signature", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > max_skew:
return False
expected = "sha256=" + hmac.new(secret, ts.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
What arrives
The body is exactly the result/v1 envelope described in the results reference (reference coming soon). A run as SYSTEM sends the combined shape with a results list, one entry per person; see Deploying.