Webhooks let Netpicker notify another system as soon as a backup, compliance run, or automation job finishes. A webhook can open a ticket, post a message, update a dashboard, or start another workflow—without anyone having to poll Netpicker for the result.
This guide explains the payload Netpicker sends, how to shape it with Jinja2, and how to build useful integrations from the three webhook events currently supported by the API.
Watch the quick guide
How a Netpicker webhook works
You configure a webhook with a name, one or more events, an HTTP method, a destination URL, optional headers, and an optional Jinja2 body template. When a selected process finishes, Netpicker renders the template and sends the resulting content to the configured URL.
For a JSON endpoint, add this header:
{
"Content-Type": "application/json"
}
Every event starts with the same envelope:
{
"event": "finish:backup-config",
"payload": {
"...": "event-specific data"
}
}
The template receives four useful variables:
| Variable | What it contains | Example |
|---|---|---|
event |
The event name as a string. | finish:backup-config |
payload |
The event-specific result object. | {{ payload | tojson }} |
__ctx__ |
The complete envelope containing both event and payload. |
{{ __ctx__ | tojson }} |
__name__ |
The name of the webhook configuration. | {{ __name__ | tojson }} |
The simplest possible JSON template forwards the complete event unchanged:
{{ __ctx__ | tojson }}
Supported events
| Event | When it fires | Typical use |
|---|---|---|
finish:backup-config |
After a configuration archive uploaded by a worker has been processed. | Create a ticket for failed backups or record configuration changes. |
finish:execute-rules |
After a compliance rule run finishes. | Notify a monitoring or ticketing system about compliance outcomes. |
finish:exec-job |
After an automation job finishes. | Report per-device job success and output. |
Configuration backup results
The finish:backup-config payload is an object keyed by device IP address or hostname. A successful entry contains the stored configuration summary, the device name, whether the configuration changed, and a calculated success value.
{
"event": "finish:backup-config",
"payload": {
"192.168.60.190": {
"id": "183901632300949504",
"upload_date": "2026-10-18T10:14:23.145000",
"file_size": 10774,
"digest": "3a616657755a8c66b5dc8f237fc2f8b5027d7e6eddbede818298c41a62531d76",
"commit": "6493afb1f1007651551de6c4f9ada34890cb823e",
"changed": false,
"name": "edge-router-01",
"success": true
}
}
}
A failed backup is identified by success: false. Depending on where the backup failed, the entry can contain an error string in content or an otherwise incomplete configuration summary.
{
"event": "finish:backup-config",
"payload": {
"192.168.60.191": {
"changed": false,
"content": "Connection timed out",
"success": false
}
}
}
If a worker result has no backup content, that device is not added to this webhook payload. Integrations should therefore evaluate the entries that are present rather than assuming that every device in a scheduled run will appear in one request.
Use case: create one ticket containing all failed backups
A single callback can contain several devices. The following template collects every failed device into one JSON array that a ticketing system can use to create a single incident:
{% set failed = [] %}
{% for ip, result in payload.items() %}
{% if result is none or result.get('success') is sameas false %}
{% set _ = failed.append({
'ipaddress': ip,
'details': result
}) %}
{% endif %}
{% endfor %}
{
"event": {{ event | tojson }},
"webhook": {{ __name__ | tojson }},
"failed_count": {{ failed | length }},
"failed_devices": {{ failed | tojson }}
}
Example output:
{
"event": "finish:backup-config",
"webhook": "Backup failures to ticketing",
"failed_count": 2,
"failed_devices": [
{
"ipaddress": "192.168.60.191",
"details": {
"changed": false,
"content": "Connection timed out",
"success": false
}
},
{
"ipaddress": "192.168.60.192",
"details": {
"changed": false,
"content": "Authentication failed",
"success": false
}
}
]
}
Important batching detail: Netpicker emits this event after each uploaded worker archive is processed. One archive can contain several devices, so this is not one request per device. If a tenant uses multiple workers or produces multiple archives, the receiving system can receive multiple requests for the same overall backup run.
Important filtering detail: Jinja2 controls the request body, but it does not cancel the HTTP request. When every backup succeeds, the example still sends a request with failed_count: 0. Configure the receiving workflow to create a ticket only when the count is greater than zero. If the receiver supports idempotency or correlation rules, use the webhook name and device details to prevent duplicate tickets across worker archives.
Example: send a readable backup summary
This template creates a compact text field suitable for chat tools and ticket descriptions:
{
"text": "Configuration backup finished\n{% for ip, result in payload.items() %}{{ ip }}{% if result and result.get('success') %}: success (changed={{ result.get('changed') }}){% else %}: FAILED{% endif %}\n{% endfor %}"
}
Compliance rule results
The finish:execute-rules event is sent once the complete compliance run finishes. Its results field is an object keyed by device, with outcome counts for each device. The remaining fields describe who started the run, the filters used, and the batch identifier.
{
"event": "finish:execute-rules",
"payload": {
"results": {
"192.168.60.190": {
"MEDIUM": 3,
"SUCCESS": 3
},
"192.168.60.191": {
"HIGH": 1,
"SUCCESS": 5
}
},
"initiator": "[email protected]",
"devices": [],
"policies": ["CIS baseline"],
"rules": [],
"tags": ["production"],
"batch_id": "28819c59-5788-48d0-a377-02ed9b3a9a98"
}
}
Empty arrays are meaningful: they indicate that no explicit value was supplied for that filter. For example, an empty rules array means the run was not narrowed to named rules.
Example: forward only devices with high-severity findings
{% set affected = [] %}
{% for device, summary in payload.results.items() %}
{% if summary.get('HIGH', 0) > 0 %}
{% set _ = affected.append({
'device': device,
'high_findings': summary.get('HIGH', 0)
}) %}
{% endif %}
{% endfor %}
{
"event": {{ event | tojson }},
"batch_id": {{ payload.batch_id | tojson }},
"affected_count": {{ affected | length }},
"affected_devices": {{ affected | tojson }}
}
As with backup events, this template does not stop delivery when affected_count is zero. Apply the final “create incident” condition in the receiving system.
Automation job results
The finish:exec-job event reports the result of an automation job. Results are keyed by device. Each device has a Boolean success field and a result field containing the job’s returned value. If execution raises an exception, Netpicker records success: false and result: null.
{
"event": "finish:exec-job",
"payload": {
"job": "Upgrade interface description",
"results": {
"192.168.60.190": {
"success": true,
"result": "Interface description updated"
},
"192.168.60.191": {
"success": false,
"result": null
}
},
"initiator": "[email protected]",
"devices": ["192.168.60.190", "192.168.60.191"],
"tags": ["branch-office"],
"batch_id": "98bddda0-e8fe-40c1-98dd-f1a6ecaf5dc4"
}
}
Example: send only failed job results in the body
{% set failed = [] %}
{% for device, outcome in payload.results.items() %}
{% if outcome.get('success') is sameas false %}
{% set _ = failed.append({
'device': device,
'result': outcome.get('result')
}) %}
{% endif %}
{% endfor %}
{
"event": {{ event | tojson }},
"job": {{ payload.job | tojson }},
"batch_id": {{ payload.batch_id | tojson }},
"failed_count": {{ failed | length }},
"failed_devices": {{ failed | tojson }}
}
Template tips
- Use
| tojsonwhenever you insert strings, objects, or arrays into JSON. It handles quoting and escaping for you. - Use
payload.items()when you need both a device key and its result. - Use
result.get('field')for optional fields so a missing value does not break the template. - Keep the final rendered body valid for the receiving endpoint. A JSON endpoint normally expects the
Content-Type: application/jsonheader. - The URL, method, and headers are static webhook settings. Jinja2 is rendered only for the request body.
Delivery behavior and troubleshooting
- Netpicker sends the request with the configured HTTP method, URL, headers, and rendered body.
- A successful destination should return a 2xx HTTP status.
- Netpicker logs non-2xx responses. The current dispatcher does not raise those responses for automatic task retry, so the receiving endpoint should be reliable and return promptly.
- If template rendering fails, Netpicker logs the rendering error and uses the error text as the request content. Test templates with representative success, failure, empty, and multi-device payloads before relying on them in production.
Need help with an integration?
If you would like help mapping one of these payloads to your ticketing, monitoring, or chat system, start a conversation with the Netpicker team.
