Webhooks
Peering Manager can be configured to transmit outgoing webhooks to remote systems in response to internal object changes.
Each webhook must be associated with at least one Peering Manager object type
and at least one event (create, update, or delete). Users can specify the
receiver URL, HTTP request type (GET
, POST
, etc.), content type, and
headers. A request body can also be specified; if left blank, this will
default to a serialized representation of the affected object.
Security Notice
Webhooks support the inclusion of user-submitted code to generate the URL, custom headers, and payloads, which may pose security risks under certain conditions. Only grant permission to create or modify webhooks to trusted users.
Jinja2 Template Support
Jinja2 templating is supported for the
URL
, additional_headers
and body_template
fields. This enables the user
to convey object data in the request headers as well as to craft a customized
request body. Request content can be crafted to enable the direct interaction
with external systems by ensuring the outgoing message is in a format the
receiver expects and understands.
For example, you might create a Peering Manager webhook to trigger a Slack message any time an IXP sessions is created. You can accomplish this using the following configuration:
- Object type: Peering > Internet Exchange Peering Session
- HTTP method:
POST
- URL: Slack incoming webhook URL
- HTTP content type:
application/json
- Body template:
{"text": "IXP session {{ data['address'] }} was created by {{ username }}."}
Available Context
The following data is available as context for Jinja2 templates:
event
- The type of event which triggered the webhook: created, updated, or deleted.model
- The NetBox model which triggered the change.timestamp
- The time at which the event occurred (in ISO 8601 format).username
- The name of the user account associated with the change.request_id
- The unique request ID. This may be used to correlate multiple changes associated with a single request.data
- A detailed representation of the object in its current state. This is typically equivalent to the model's representation in Peering Manager's REST API.snapshots
- Minimal "snapshots" of the object state both before and after the change was made; provided as a dictionary with keys namedprechange
andpostchange
. These are not as extensive as the fully serialized representation, but contain enough information to convey what has changed.
Default Request Body
If no body template is specified, the request body will be populated with a JSON object containing the context data. For example, a newly created site might appear as follows:
{
"event": "create",
"timestamp": "2023-08-07 19:30:23.140019+00:00",
"model": "internetexchangepeeringsession",
"username": "admin",
"request_id": "af7aebe6-d049-405a-910f-d01e5ff043cd",
"data": {
"id": 85,
"display": "S.H.I.E.L.D. Internet Exchange - AS64498 - IP 2001:db8::fbf2:1",
"service_reference": null,
"autonomous_system": {
"id": 9,
"url": "/api/peering/autonomous-systems/9/",
"display": "AS64498 - Stark Industris",
"asn": 64498,
"name": "Stark Industries",
"ipv6_max_prefixes": 500,
"ipv4_max_prefixes": 500
},
"ixp_connection": {
"id": 1,
"url": "/api/net/connections/1/",
"display": "S.H.I.E.L.D. Internet Exchange",
"name": "S.H.I.E.L.D. Internet Exchange",
"mac_address": "1c:b8:5f:c0:d0:28",
"ipv6_address": "2001:db8::fbf4:1/64",
"ipv4_address": null
},
"ip_address": "2001:db8::fbf2:1",
"status": {
"value": "requested",
"label": "Requested"
},
"password": null,
"encrypted_password": null,
"multihop_ttl": 1,
"is_route_server": false,
"import_routing_policies": [],
"export_routing_policies": [],
"communities": [],
"local_context_data": null,
"exists_in_peeringdb": false,
"is_abandoned": false,
"bgp_state": null,
"received_prefix_count": 0,
"advertised_prefix_count": 0,
"last_established_state": null,
"comments": "",
"tags": [],
"created": "2023-08-07T21:30:22.872649+02:00",
"updated": "2023-08-07T21:30:22.872893+02:00"
},
"snapshots": {
"prechange": null,
"postchange": {
"created": "2023-08-07T19:30:22.872Z",
"updated": "2023-08-07T19:30:22.872Z",
"local_context_data": null,
"description": "",
"comments": "",
"service_reference": null,
"autonomous_system": 2,
"ip_address": "2001:db8::fbf2:1",
"status": "requested",
"password": null,
"encrypted_password": null,
"multihop_ttl": 1,
"bgp_state": null,
"received_prefix_count": 0,
"advertised_prefix_count": 0,
"last_established_state": null,
"ixp_connection": 1,
"is_route_server": false,
"import_routing_policies": [],
"export_routing_policies": [],
"communities": [],
"tags": []
}
}
}
Conditional Webhooks
A webhook may include a set of conditional logic expressed in JSON used to
control whether a webhook triggers for a specific object. For example, you
may wish to trigger a webhook for devices only when the status
field of an
object is "enabled":
{
"and": [
{
"attr": "status.value",
"value": "enabled"
}
]
}
Even though a webhook should fire when only one condition is triggered, the
condition must be included in a set, hence the and
key in this example.
For more detail, see the reference documentation for Peering Manager's conditional logic.
Webhook Processing
When a change is detected, any resulting webhooks are placed into a Redis
queue for processing. This allows the user's request to complete without
needing to wait for the outgoing webhook(s) to be processed. The webhooks
are then extracted from the queue by the rqworker
process and HTTP requests
are sent to their respective destinations. The current webhook queue and any
failed webhooks can be inspected in the admin UI under System > Background
Tasks.
A request is considered successful if the response has a 2XX status code; otherwise, the request is marked as having failed. Failed requests may be retried manually via the admin UI.
Troubleshooting
To assist with verifying that the content of outgoing webhooks is rendered
correctly, Peering Manager provides a simple HTTP listener that can be run
locally to receive and display webhook requests. First, modify the target URL
of the desired webhook to http://localhost:9000/
. This will instruct Peering
Manager to send the request to the local server on TCP port 9000. Then, start
the webhook receiver service from the Peering Manager root directory:
$ python manage.py webhook_receiver
Listening on port http://localhost:9000. Stop with CONTROL-C.
You can test the receiver itself by sending any HTTP request to it. For example:
$ curl -X POST http://localhost:9000 --data '{"foo": "bar"}'
The server will print output similar to the following:
[1] Tue, 07 Apr 2020 17:44:02 GMT 127.0.0.1 "POST / HTTP/1.1" 200 -
Host: localhost:9000
User-Agent: curl/7.58.0
Accept: */*
Content-Length: 14
Content-Type: application/x-www-form-urlencoded
{"foo": "bar"}
------------
Note that webhook_receiver
does not actually do anything with the
information received: It merely prints the request headers and body for
inspection.
Now, when the Peering Manager webhook is triggered and processed, you should
see its headers and content appear in the terminal where the webhook receiver
is listening. If you don't, check that the rqworker
process is running and
that webhook events are being placed into the queue (visible under the Peering
Manager admin UI).