Repository: frappe/frappe

Issue:
[Proposal] Add Parameters to Webhook Request URL
We use webhooks to connect erpnext to other apps and systems.

Most REST-Apis contain some ids in their URL. Even Frappe does (`/api/resource/{{doctype}}/{{docname}}`)

AFAIK, there is no way to dynamically set the Webhook request URL with contents of the document.


I propose to render the webhook-url as a template, much the same as the json body is. This would allow to call endpoints like
`http://example.com/api/{{doc.some_field}}`





Discussion:
https://github.com/aakvatech/Bulk-Webhook/

Check this out

Resolving patch:
diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py
--- a/frappe/integrations/doctype/webhook/webhook.py
+++ b/frappe/integrations/doctype/webhook/webhook.py
@@ -115,29 +115,34 @@ def enqueue_webhook(doc, webhook) -> None:
 	webhook: Webhook = frappe.get_doc("Webhook", webhook.get("name"))
 	headers = get_webhook_headers(doc, webhook)
 	data = get_webhook_data(doc, webhook)
-	r = None
 
+	if webhook.is_dynamic_url:
+		request_url = frappe.render_template(webhook.request_url, get_context(doc))
+	else:
+		request_url = webhook.request_url
+
+	r = None
 	for i in range(3):
 		try:
 			r = requests.request(
 				method=webhook.request_method,
-				url=webhook.request_url,
+				url=request_url,
 				data=json.dumps(data, default=str),
 				headers=headers,
 				timeout=5,
 			)
 			r.raise_for_status()
 			frappe.logger().debug({"webhook_success": r.text})
-			log_request(webhook.name, doc.name, webhook.request_url, headers, data, r)
+			log_request(webhook.name, doc.name, request_url, headers, data, r)
 			break
 
 		except requests.exceptions.ReadTimeout as e:
 			frappe.logger().debug({"webhook_error": e, "try": i + 1})
-			log_request(webhook.name, doc.name, webhook.request_url, headers, data)
+			log_request(webhook.name, doc.name, request_url, headers, data)
 
 		except Exception as e:
 			frappe.logger().debug({"webhook_error": e, "try": i + 1})
-			log_request(webhook.name, doc.name, webhook.request_url, headers, data, r)
+			log_request(webhook.name, doc.name, request_url, headers, data, r)
 			sleep(3 * i + 1)
 			if i != 2:
 				continue


Test patch:
diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py
--- a/frappe/integrations/doctype/webhook/test_webhook.py
+++ b/frappe/integrations/doctype/webhook/test_webhook.py
@@ -206,3 +206,59 @@ def test_webhook_with_array_body(self):
 			enqueue_webhook(doc, wh)
 			log = frappe.get_last_doc("Webhook Request Log")
 			self.assertEqual(len(json.loads(log.response)["json"]), 3)
+
+	def test_webhook_with_dynamic_url_enabled(self):
+		wh_config = {
+			"doctype": "Webhook",
+			"webhook_doctype": "Note",
+			"webhook_docevent": "after_insert",
+			"enabled": 1,
+			"request_url": "https://httpbin.org/anything/{{ doc.doctype }}",
+			"is_dynamic_url": 1,
+			"request_method": "POST",
+			"request_structure": "JSON",
+			"webhook_json": "{}",
+			"meets_condition": "Yes",
+			"webhook_headers": [
+				{
+					"key": "Content-Type",
+					"value": "application/json",
+				}
+			],
+		}
+
+		with get_test_webhook(wh_config) as wh:
+			doc = frappe.new_doc("Note")
+			doc.title = "Test Webhook Note"
+			enqueue_webhook(doc, wh)
+			log = frappe.get_last_doc("Webhook Request Log")
+			self.assertEqual(json.loads(log.response)["url"], "https://httpbin.org/anything/Note")
+
+	def test_webhook_with_dynamic_url_disabled(self):
+		wh_config = {
+			"doctype": "Webhook",
+			"webhook_doctype": "Note",
+			"webhook_docevent": "after_insert",
+			"enabled": 1,
+			"request_url": "https://httpbin.org/anything/{{doc.doctype}}",
+			"is_dynamic_url": 0,
+			"request_method": "POST",
+			"request_structure": "JSON",
+			"webhook_json": "{}",
+			"meets_condition": "Yes",
+			"webhook_headers": [
+				{
+					"key": "Content-Type",
+					"value": "application/json",
+				}
+			],
+		}
+
+		with get_test_webhook(wh_config) as wh:
+			doc = frappe.new_doc("Note")
+			doc.title = "Test Webhook Note"
+			enqueue_webhook(doc, wh)
+			log = frappe.get_last_doc("Webhook Request Log")
+			self.assertEqual(
+				json.loads(log.response)["url"], "https://httpbin.org/anything/{{doc.doctype}}"
+			)