- The URL to send the request to
- The request body as text or as key-value pairs, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below.
+- Encoding for the request body, either JSON or form data
- The request headers as key-value pairs
#### Workflow placeholders
<input type="hidden" formControlName="id" />
<div class="col">
<pngx-input-text i18n-title title="Webhook url" formControlName="url" [error]="error?.actions?.[i]?.url"></pngx-input-text>
- <pngx-input-switch i18n-title title="Use parameters for webhook body" formControlName="use_params"></pngx-input-switch>
+ <div class="d-flex">
+ <pngx-input-switch i18n-title title="Use parameters for webhook body" formControlName="use_params" [horizontal]="true"></pngx-input-switch>
+ <pngx-input-switch i18n-title title="Send webhook payload as JSON" formControlName="as_json" [horizontal]="true" class="ms-5"></pngx-input-switch>
+ </div>
@if (formGroup.get('webhook').value['use_params']) {
<pngx-input-entries i18n-title title="Webhook params" formControlName="params" [error]="error?.actions?.[i]?.params"></pngx-input-entries>
} @else {
id: new FormControl(action.webhook?.id),
url: new FormControl(action.webhook?.url),
use_params: new FormControl(action.webhook?.use_params),
+ as_json: new FormControl(action.webhook?.as_json),
params: new FormControl(action.webhook?.params),
body: new FormControl(action.webhook?.body),
headers: new FormControl(action.webhook?.headers),
id: null,
url: null,
use_params: true,
+ as_json: false,
params: null,
body: null,
headers: null,
use_params?: boolean
+ as_json?: boolean
+
params?: object
body?: string
--- /dev/null
+# Generated by Django 5.1.4 on 2025-01-18 19:35
+
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("documents", "1060_alter_customfieldinstance_value_select"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="workflowactionwebhook",
+ name="as_json",
+ field=models.BooleanField(default=False, verbose_name="send as JSON"),
+ ),
+ ]
verbose_name=_("use parameters"),
)
+ as_json = models.BooleanField(
+ default=False,
+ verbose_name=_("send as JSON"),
+ )
+
params = models.JSONField(
_("webhook parameters"),
null=True,
"id",
"url",
"use_params",
+ "as_json",
"params",
"body",
"headers",
max_retries=3,
throws=(httpx.HTTPError,),
)
-def send_webhook(url, data, headers, files):
+def send_webhook(
+ url: str,
+ data: str | dict,
+ headers: dict,
+ files: dict,
+ *,
+ as_json: bool = False,
+):
try:
- httpx.post(
- url,
- data=data,
- files=files,
- headers=headers,
- ).raise_for_status()
+ if as_json:
+ httpx.post(
+ url,
+ json=data,
+ files=files,
+ headers=headers,
+ ).raise_for_status()
+ else:
+ httpx.post(
+ url,
+ data=data,
+ files=files,
+ headers=headers,
+ ).raise_for_status()
logger.info(
f"Webhook sent to {url}",
)
data=data,
headers=headers,
files=files,
+ as_json=action.webhook.as_json,
)
logger.debug(
f"Webhook to {action.webhook.url} queued",
from guardian.shortcuts import get_groups_with_perms
from guardian.shortcuts import get_users_with_perms
from httpx import HTTPStatusError
+from pytest_httpx import HTTPXMock
from rest_framework.test import APITestCase
from documents.signals.handlers import run_workflows
data=f"Test message: http://localhost:8000/documents/{doc.id}/",
headers={},
files=None,
+ as_json=False,
)
@override_settings(
data=f"Test message: http://localhost:8000/documents/{doc.id}/",
headers={},
files={"file": ("simple.pdf", mock.ANY, "application/pdf")},
+ as_json=False,
)
@override_settings(
)
mock_post.assert_called_once()
+
+
+class TestWebhookSend:
+ def test_send_webhook_data_or_json(
+ self,
+ httpx_mock: HTTPXMock,
+ ):
+ """
+ GIVEN:
+ - Nothing
+ WHEN:
+ - send_webhook is called with data or dict
+ THEN:
+ - data is sent as form-encoded and json, respectively
+ """
+ httpx_mock.add_response(
+ content=b"ok",
+ )
+
+ send_webhook(
+ url="http://paperless-ngx.com",
+ data="Test message",
+ headers={},
+ files=None,
+ as_json=False,
+ )
+ assert httpx_mock.get_request().headers.get("Content-Type") is None
+ httpx_mock.reset()
+
+ httpx_mock.add_response(
+ json={"status": "ok"},
+ )
+ send_webhook(
+ url="http://paperless-ngx.com",
+ data={"message": "Test message"},
+ headers={},
+ files=None,
+ as_json=True,
+ )
+ assert httpx_mock.get_request().headers["Content-Type"] == "application/json"