diff --git a/docs/integrations/webhooks.md b/docs/integrations/webhooks.md index 4a14db7ba..966655925 100644 --- a/docs/integrations/webhooks.md +++ b/docs/integrations/webhooks.md @@ -17,7 +17,7 @@ For example, you might create a NetBox webhook to [trigger a Slack message](http * HTTP method: `POST` * URL: Slack incoming webhook URL * HTTP content type: `application/json` -* Body template: `{"text": "IP address {{ data['address'] }} was created by {{ username }}!"}` +* Body template: `{"text": "IP address {{ data['address'] }} was created by {{ request.user }}!"}` ### Available Context @@ -35,13 +35,6 @@ The following data is available as context for Jinja2 templates: * `request.user` - The name of the authenticated user who made the request (if available) * `data` - A detailed representation of the object in its current state. This is typically equivalent to the model's representation in NetBox's REST API. * `snapshots` - Minimal "snapshots" of the object state both before and after the change was made; provided as a dictionary with keys named `prechange` and `postchange`. These are not as extensive as the fully serialized representation, but contain enough information to convey what has changed. -* ⚠️ `request_id` - The unique request ID. This may be used to correlate multiple changes associated with a single request. -* ⚠️ `username` - The name of the user account associated with the change. - -!!! warning "Deprecation of legacy keys" - The `request_id` and `username` keys in the webhook payload above are deprecated and should no longer be used. Support for them will be removed in NetBox v4.7.0. - - Use `request.user` and `request.id` from the `request` object included in the callback context instead. ### Default Request Body @@ -52,8 +45,6 @@ If no body template is specified, the request body will be populated with a JSON "event": "created", "timestamp": "2026-03-06T15:11:23.503186+00:00", "object_type": "dcim.site", - "username": "jstretch", - "request_id": "17af32f0-852a-46ca-a7d4-33ecd0c13de6", "data": { "id": 4, "url": "/api/dcim/sites/4/", diff --git a/docs/models/extras/webhook.md b/docs/models/extras/webhook.md index 7f0f322c6..e35fe9faf 100644 --- a/docs/models/extras/webhook.md +++ b/docs/models/extras/webhook.md @@ -84,10 +84,9 @@ The following context variables are available to the text and link templates. | `event` | The event type (`create`, `update`, or `delete`) | | `timestamp` | The time at which the event occurred | | `object_type` | The type of object impacted (`app_label.model_name`) | -| `username` | The name of the user associated with the change | -| `request_id` | The unique request ID | | `data` | A complete serialized representation of the object | | `snapshots` | Pre- and post-change snapshots of the object | +| `request` | Data about the triggering request (if available) | -!!! warning "Deprecation of legacy fields" - The `request_id` and `username` fields in the webhook payload above are deprecated and should no longer be used. Support for them will be removed in NetBox v4.7.0. Use `request.user` and `request.id` from the `request` object included in the callback context instead. (Note that `request` is populated in the context only when the webhook is associated with a triggering request.) +!!! note + The `request` variable is populated in the context only when the webhook is associated with a triggering request. It exposes `request.id` (the unique request ID) and `request.user` (the name of the user associated with the change), among other attributes. diff --git a/docs/plugins/development/webhooks.md b/docs/plugins/development/webhooks.md index 4771e4c3b..d67c800f9 100644 --- a/docs/plugins/development/webhooks.md +++ b/docs/plugins/development/webhooks.md @@ -29,8 +29,6 @@ The resulting webhook payload will look like the following: "event": "updated", "timestamp": "2025-08-07T14:24:30.627321+00:00", "object_type": "dcim.site", - "username": "admin", - "request_id": "49e3e39e-7333-4b9c-a9af-19f0dc1e7dc9", "data": { "id": 2, "url": "/api/dcim/sites/2/", @@ -44,11 +42,6 @@ The resulting webhook payload will look like the following: } ``` -!!! warning "Deprecation of legacy keys" - The `request_id` and `username` keys in the webhook payload above are deprecated and should no longer be used. Support for them will be removed in NetBox v4.7.0. - - Use `request.user` and `request.id` from the `request` object included in the callback context instead. - !!! note "Consider namespacing webhook data" The data returned from all webhook callbacks will be compiled into a single `context` dictionary. Any existing keys within this dictionary will be overwritten by subsequent callbacks which include those keys. To avoid collisions with webhook data provided by other plugins, consider namespacing your plugin's data within a nested dictionary as such: diff --git a/netbox/extras/events.py b/netbox/extras/events.py index c01e02ed5..23a111937 100644 --- a/netbox/extras/events.py +++ b/netbox/extras/events.py @@ -154,9 +154,6 @@ def enqueue_event(queue, instance, request, event_type): snapshots=get_snapshots(instance, event_type), request=request, user=request.user, - # Legacy request attributes for backward compatibility - username=request.user.username, # DEPRECATED, will be removed in NetBox v4.7.0 - request_id=request.id, # DEPRECATED, will be removed in NetBox v4.7.0 ) # For delete events, eagerly serialize the payload before the row is gone. @@ -170,15 +167,11 @@ def process_event_rules(event_rules, object_type, event): Process a list of EventRules against an event. Notes on event sources: - - Object change events (created/updated/deleted) are enqueued via - enqueue_event() during an HTTP request. - These events include a request object and legacy request - attributes (e.g. username, request_id) for backward compatibility. - - Job lifecycle events (JOB_STARTED/JOB_COMPLETED) are emitted by - job_start/job_end signal handlers and may not include a request - context. - Consumers must not assume that fields like `username` are always - present. + - Object change events (created/updated/deleted) are enqueued via enqueue_event() + during an HTTP request. These events include a request object. + - Job lifecycle events (JOB_STARTED/JOB_COMPLETED) are emitted by job_start/job_end + signal handlers and may not include a request context. Consumers must not assume + that a request is always present. """ for event_rule in event_rules: @@ -215,12 +208,6 @@ def process_event_rules(event_rules, object_type, event): queue_name = get_config().QUEUE_MAPPINGS.get('webhook', RQ_QUEUE_DEFAULT) rq_queue = get_queue(queue_name) - # For job lifecycle events, `username` may be absent because - # there is no request context. - # Prefer the associated user object when present, falling - # back to the legacy username attribute. - username = getattr(event.get('user'), 'username', None) or event.get('username') - # Compile the task parameters params = { 'event_rule': event_rule, @@ -229,7 +216,6 @@ def process_event_rules(event_rules, object_type, event): 'data': event_data, 'snapshots': event.get('snapshots'), 'timestamp': timezone.now().isoformat(), - 'username': username, 'retry': get_rq_retry(), } if 'request' in event: diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 969dbb622..18bb6b573 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -220,7 +220,7 @@ class Webhook(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, OwnerMixin, Ch help_text=_( "Jinja2 template for a custom request body. If blank, a JSON object representing the change will be " "included. Available context data includes: event, model, " - "timestamp, username, request_id, and data." + "timestamp, request, and data." ) ) secret = models.CharField( diff --git a/netbox/extras/tests/test_event_rules.py b/netbox/extras/tests/test_event_rules.py index ba8226aec..c9722b0d7 100644 --- a/netbox/extras/tests/test_event_rules.py +++ b/netbox/extras/tests/test_event_rules.py @@ -396,8 +396,6 @@ class EventRuleTestCase(RQQueueTestMixin, APITestCase): self.assertEqual(body['event'], 'created') self.assertEqual(body['timestamp'], job.kwargs['timestamp']) self.assertEqual(body['object_type'], 'dcim.site') - self.assertEqual(body['username'], 'testuser') - self.assertEqual(body['request_id'], str(request_id)) self.assertEqual(body['data']['name'], 'Site 1') self.assertEqual(body['data']['foo'], 1) self.assertEqual(body['context']['foo'], 123) # From netbox.tests.dummy_plugin @@ -431,13 +429,13 @@ class EventRuleTestCase(RQQueueTestMixin, APITestCase): with patch.object(Session, 'send', dummy_send): send_webhook(**job.kwargs) - def test_job_completed_webhook_username_fallback(self): + def test_job_completed_webhook_without_request(self): """ Ensure job_end event processing can enqueue a webhook even when the EventContext - lacks legacy request attributes (e.g. `username`). + lacks a request context. The job_start/job_end signal receivers only populate `user` and `data`, so webhook - processing must derive the username from the user object (or tolerate it being unset). + processing must tolerate the absence of a request. """ script_type = ObjectType.objects.get_for_model(Script) webhook_type = ObjectType.objects.get_for_model(Webhook) @@ -459,7 +457,7 @@ class EventRuleTestCase(RQQueueTestMixin, APITestCase): self.assertEqual(job.kwargs['event_rule'], event_rule) self.assertEqual(job.kwargs['event_type'], JOB_COMPLETED) self.assertEqual(job.kwargs['object_type'], script_type) - self.assertEqual(job.kwargs['username'], self.user.username) + self.assertNotIn('request', job.kwargs) def test_duplicate_enqueue_refreshes_lazy_payload(self): """ diff --git a/netbox/extras/webhooks.py b/netbox/extras/webhooks.py index 076f044fa..b750d5879 100644 --- a/netbox/extras/webhooks.py +++ b/netbox/extras/webhooks.py @@ -43,7 +43,7 @@ def generate_signature(request_body, secret): @job('default') -def send_webhook(event_rule, object_type, event_type, data, timestamp, username, request=None, snapshots=None): +def send_webhook(event_rule, object_type, event_type, data, timestamp, request=None, snapshots=None): """ Make a POST request to the defined Webhook """ @@ -54,8 +54,6 @@ def send_webhook(event_rule, object_type, event_type, data, timestamp, username, 'event': WEBHOOK_EVENT_TYPES.get(event_type, event_type), 'timestamp': timestamp, 'object_type': '.'.join(object_type.natural_key()), - 'username': username, - 'request_id': request.id if request else None, 'data': data, } if request: