Generic Webhook
The Generic Webhook integration is used to create incidents on event triggers. The trigger can be any query posted to the integration.
Utilities · Generic Webhook
Details
| ID | Generic Webhook |
|---|---|
| Provider | Open Source |
| Category | Utilities |
| From Version | 5.5.0 |
| Docker Image | demisto/fastapi:0.125.0.10158186 |
| Supported Modules | Agentix XSIAM |
README
The Generic Webhook integration enables you to push events to Cortex XSOAR or Cortex XSIAM. The Generic Webhook integration can be used when there is no relevant integration, when the integration does not match your organization’s needs, when there are restrictions on pulling information from the third-party solution, or when the triggering source is not an event that can be fetched by Cortex XSOAR or Cortex XSIAM, such as a slack message or a completed form.
Example: Your organization’s employees fill out a Google form to report security incidents. Employees specify the type of incident in a drop-down field with predefined options, the Generic Webhook integration pushes the event into Cortex XSOAR or Cortex XSIAM, and a playbook runs. The incident type and the specific playbook is determined by the incident type field in the drop-down.
The Generic Webhook integration creates incidents in Cortex XSOAR, alerts in Cortex XSIAM 2.x or issues in Cortex XSIAM 3.x. The trigger can be any query posted to the integration.
The Generic Webhook integration is a long-running integration. For more information about long-running integrations, see the Cortex XSOAR 8 Cloud, Cortex XSOAR 8 On-prem or Cortex XSIAM documentation.
To use the Generic Webhook integration, you need to complete the following steps:
- Configure Generic Webhook in Cortex XSOAR or Cortex XSIAM.
- Set up authentication.
- Determine the webhook URL.
- Trigger the webhook to generate incidents, alerts, or issues.
Configure Generic Webhook on Cortex XSOAR or Cortex XSIAM
- In the Cortex XSOAR or Cortex XSIAM integrations page, search for Generic Webhook and click Add instance to create and configure a new integration instance.
| Parameter | Description | Required |
|---|---|---|
| Listen Port Note: This field only appears in Cortex XSOAR 8 and Cortex XSIAM if you are using an engine. It always appears in Cortex 6.x. |
Runs the service on this port. Requires a unique port for each long-running integration instance. Do not use the same port for multiple instances. Note: If you click the test button more than once, a failure may occur, mistakenly indicating that the port is already in use. |
True |
| username | Username (see Security for more details) | For Cortex XSOAR 6.x - False For Cortex XSOAR 8 and Cortex XSIAM if the integration is running on an engine - False For Cortex XSOAR 8 and Cortex XSIAM if the integration is not running on an engine - True |
| password | Password (see Security for more details) | For Cortex XSOAR 6.x - False For Cortex XSOAR 8 and Cortex XSIAM if the integration is running on an engine - False For Cortex XSOAR 8 and Cortex XSIAM if the integration is not running on an engine - True |
| certificate | For use with HTTPS - the certificate that the service should use. Supported for Cortex XSOAR On-prem (6.x or 8). Supported for Cortex XSOAR 8 Cloud and Cortex XSIAM only when using an engine. Cortex XSOAR 8 Cloud tenants and Cortex XSIAM tenants do not support custom certificates. |
False |
| Private Key | For use with HTTPS - the private key that the service should use. Supported for Cortex XSOAR On-prem (6.x or 8). Supported for Cortex XSOAR 8 Cloud and Cortex XSIAM only when using an engine. Cortex XSOAR 8 Cloud tenants and Cortex XSIAM tenants do not support private keys. |
False |
| Result | Automatically generated webhook trigger link (based on user configuration). | Auto-populated. Note: This field does not appear for Cortex XSOAR 6.x or Cortex XSOAR 8.9 On-prem |
| incidentType | Incident, issue, or alert type | False |
| store_samples | Store sample events for mapping (Because this is a push-based integration, it cannot fetch sample events in the mapping wizard). | False |
- Click Done.
- For Cortex XSOAR 6.x:
- Navigate to Settings > About > Troubleshooting.
- In the Server Configuration section, verify that the value for the instance.execute.external.<INTEGRATION-INSTANCE-NAME> key is set to true. If this key does not exist, click + Add Server Configuration and add instance.execute.external.<INTEGRATION-INSTANCE-NAME> and set the value to true. See the following reference article for further information.
Set up Authentication
Authentication options depend on the product and version.
| Product | Authentication required | Authentication options |
|---|---|---|
| Cortex XSOAR 8 Cloud tenant | Yes | - Basic authentication (username and password) |
| Cortex XSIAM tenant | Yes | - Basic authentication (username and password) |
| Cortex XSOAR 8 On-prem | No | - Basic authentication (username and password) - Custom certificate NOTE: For more information about setting up custom certificates for Cortex XSOAR 8 On-prem, see HTTPS with a signed certificate. |
| Cortex XSOAR 6.x | No | - Basic authentication (username and password) - Header-based authentication using _header:<HEADER-NAME> syntax - Custom certificate NOTE: For more information about setting up custom certificates for Cortex XSOAR 6.x, see HTTPS with a signed certificate. |
| Engines (Cortex XSOAR 8 Cloud, Cortex XSOAR 8 On-Prem, Cortex XSIAM, Cortex 6.x) | No | - Basic authentication (username and password) - Header-based authentication using _header:<HEADER-NAME> syntax (Cortex XSOAR 6.x only) - Custom certificate NOTE: For more information about setting up custom certificates for engines, see Configure an engine to use custom certificates. |
Determine the webhook URL
For Cortex XSIAM and Cortex XSOAR 8 Cloud, the Results section of the integration configuration provides the webhook URL. We recommend you verify the URL using the instructions below. For on-prem Cortex XSOAR 6.x and Cortex XSOAR 8, you must use the URL template below to determine the webhook URL.
Prerequisite:
For Cortex XSOAR 8 On-prem, you need to add the ext- FQDN DNS record to map the Cortex XSOAR DNS name to the external IP address.
For example, ext-xsoar.mycompany.com.
| Product | URL | Example |
|---|---|---|
| Cortex XSOAR 8 Cloud and On-prem | <ext-<CORTEX-TENANT-URL>/xsoar/instance/execute/<INTEGRATION-INSTANCE-NAME> |
https://ext-mytenant.crtx.us.paloaltonetworks.com/xsoar/instance/execute/my_instance_01 |
| Cortex XSIAM | <ext-<CORTEX-TENANT-URL>/xsoar/instance/execute/<INTEGRATION-INSTANCE-NAME> |
https://ext-mytenant.crtx.us.paloaltonetworks.com/xsoar/instance/execute/my_instance_01 |
| Cortex XSOAR 6.x | <CORTEX-XSOAR-URL>/instance/execute/<INTEGRATION-INSTANCE-NAME> |
https://my.xsoar.live/instance/execute/webhook |
Notes:
- For Cortex XSIAM, you must replace xdr in the tenant URL with crtx. For example, if your tenant URL is
https://companyname.xdr.eu.paloaltonetworks.comthe webhook URL ishttps://ext-companyname.crtx.eu.paloaltonetworks.com/xsoar/instance/execute/my_instance_01. - The string instance does not refer to the name of your Cortex instance, it is part of the URL.
- The name of the instance cannot include special characters.
- For Cortex XSOAR 6.x or if you are using an engine, if you are not invoking the integration via the server HTTPS endpoint, you can trigger the webhook URL as follows:
<CORTEX-XSOAR-URL>:<LISTEN_PORT>/For example,https://my.xsoar.live:8000/.
Usage
The Generic Webhook integration accepts POST HTTP queries, with the following optional fields in the request body:
| Field | Type | Description |
|---|---|---|
| name | string | Name of the incident, alert, or issue to be created. |
| type | string | Type of the incident, alert, or issue to be created. If not provided, the value of the integration parameter incidentType is used. |
| occurred | string | Date the incident occurred in ISO-8601 format. If not provided, the trigger time is used. |
| rawJson | object | Details of the incident, alert, or issue to be created. Headers can be found in a seperate key. For example, {"field1":"value1","field2":"value2","headers": {"header_field3": "header_value3"}} |
Note: The cURL examples below are formatted for macOS. For Windows machines, modify as needed.
Examples
Basic authentication can be used in three ways with the same username/password configured in the integration:
Using -u flag:
curl -X POST https://ext-companyname.crtx.eu.paloaltonetworks.com/xsoar/instance/execute/my_instance_01 -u "<Username:Password>" -H "Content-Type: application/json" -d '{"name":"incident created via generic webhook","rawJson":{"some_field":"some_value"}}'
Using Authorization header (where the header value is base64 encoded username:password):
curl -X POST https://ext-companyname.crtx.eu.paloaltonetworks.com/xsoar/instance/execute/my_instance_01 -H "Authorization: Basic MTIzOjEyMw==" -H "Content-Type: application/json" -d '{"name":"incident created via generic webhook","rawJson":{"some_field":"some_value"}}'
Note: MTIzOjEyMw== is Base64 encoded username:password example.
Or embedding credentials directly in the URL:
curl -X POST https://username:password@ext-companyname.crtx.eu.paloaltonetworks.com/xsoar/instance/execute/my_instance_01 -H "Content-Type: application/json" -d '{"name":"incident created via generic webhook","rawJson":{"some_field":"some_value"}}'
The request payload does not have to contain the fields mentioned above, and may include any JSON fields and values:
curl -X POST https://ext-companyname.crtx.eu.paloaltonetworks.com/xsoar/instance/execute/my_instance_01 -u "<Username:Password>" -H "Content-Type: application/json" -d '{"string_field":"string_field_value","array_field":["item1","item2"]}'
Multiple incidents, alerts, or issues can be created in one request by sending an array as the request body:
curl -X POST https://ext-companyname.crtx.eu.paloaltonetworks.com/xsoar/instance/execute/my_instance_01 -u "<Username:Password>" -H "Content-Type: application/json" -d '[{"name":"incident1","rawJson":{"some_field":"some_value"}}, {"name":"incident2","rawJson":{"some_field":"some_value"}}]'
Using custom header authentication (Cortex XSOAR 6.x only). In this example, the username in the integration instance is set to _header:Authorization and the password in the integration instance is set to Basic MYIvOkEyMw== :
curl -X POST https://ext-companyname.crtx.eu.paloaltonetworks.com/xsoar/instance/execute/my_instance_01 -H "Authorization: Basic MYIvOkEyMw==" -H "Content-Type: application/json" -d '{"name":"incident created via generic webhook","rawJson":{"some_field":"some_value"}}' -v
The response is an array containing an object with the created incident metadata, such as the incident ID.
The payload can then be mapped. For more information see:
- Create a mapper (Cortex XSOAR 6.x)
- Create a mapper (Cortex XSOAR 8 Cloud)
- Create a mapper (Cortex XSOAR 8.9 On-prem)
- Create a mapper (Cortex XSIAM)
Note: To use the mapping wizard, the Store sample events for mapping parameter must be set. Because this is a push-based integration, it cannot fetch sample events in the mapping wizard. After you finish mapping, we recommend turning off the sample events storage to reduce performance overhead.
Authorization headers
For Cortex XSOAR 6.x users, you can use the special _header:<HEADER-NAME> syntax to authenticate requests using custom headers from your third-party service. This helps prevent unauthorized creation of incidents. Set the username field in the integration to _header:<HEADER-NAME> and provide the header value in the password field.
Example: If the request included in the Authorization header the value Bearer XXX, then the username should be set to _header:Authorization and the password should be set to Bearer XXX.
Troubleshooting authorization headers
- Header Size Limit: Each server or framework may impose a limit on the total size of the headers received in a request. For example, servers such as Nginx or Apache have their own default values that can be configured. FastAPI itself doesn’t specifically limit the header size, but underlying ASGI servers like Uvicorn or Hypercorn that run FastAPI do have default limits (For example, Uvicorn has a default of 1MB for the total size of request headers).
- Allowed Characters: Headers should only use ASCII characters. Non-ASCII characters must be encoded.
- Header Names and Values: Certain characters are restricted in header names and values. Typically, names cannot include characters such as : or newlines, and values are restricted from including newlines to protect against header injection attacks.
- Case Sensitivity: Header keys are case-insensitive as per HTTP standards, but it is good practice to keep a consistent casing convention for ease of maintenance and readability.
Configuration parameters
engine_placeholder—longRunning— Long running instancecredentials— UsernamelongRunningPort— Listen Port (required)certificate— Certificate (Required for HTTPS)key— Private Key (Required for HTTPS)incidentType— Incident typestore_samples— Store sample events for mappingresultLink— After adding the Generic Webhook settings, a unique link will be generated below.
Commands (0)
This integration defines no commands.
import asyncio from http import HTTPStatus from unittest.mock import MagicMock import pytest from CommonServerPython import * # import demistomock as demisto from fastapi import Request from fastapi.testclient import TestClient from GenericWebhook import app, main, parse_incidents @pytest.fixture def client(): return TestClient(app) def test_handle_post_single_incident(mocker, client): """ Given: A bodu that is one incident When: Sending a post request Then: The body is parsed properly """ incident_data = { "name": "Test Incident", "type": "Test Type", "occurred": "2024-03-17T12:00:00Z", "rawJson": {"key": "value"}, } return_incidents = [{"name": "something"}] create_incidents = mocker.patch.object(demisto, "createIncidents", return_value=return_incidents) response = client.post("/", json=incident_data) called_arg = create_incidents.call_args_list[0].args[0] assert isinstance(called_arg, list) assert len(called_arg) == 1 assert response.status_code == HTTPStatus.OK assert response.json() == return_incidents def test_handle_post_multiple_incident(mocker, client): """ Given: A bodu that is an array of incidents When: Sending a post request Then: The body is parsed properly """ incident_data = [ {"name": "Test Incident", "type": "Test Type", "occurred": "2024-03-17T12:00:00Z", "raw_json": {"key": "value"}}, {"name": "Test Incident2", "type": "Test Type", "occurred": "2024-03-17T12:00:00Z", "raw_json": {"key": "value"}}, ] return_incidents = [{"name": "something"}] create_incidents = mocker.patch.object(demisto, "createIncidents", return_value=return_incidents) response = client.post("/", json=incident_data) called_arg = create_incidents.call_args_list[0].args[0] assert isinstance(called_arg, list) assert len(called_arg) == 2 assert response.status_code == HTTPStatus.OK assert response.json() == return_incidents def test_handle_post_with_invalid_credentials(mocker, client): """ Given: a server that expects a user and password When: Calling post with bad credentials Then:a 401 response code is recieved """ mocker.patch.object(demisto, "params", return_value={"credentials": {"identifier": "user", "password": "pass"}}) response = client.post("/", json=[{"name": "Test Incident"}], auth=("invalid_username", "invalid_password")) assert response.status_code == HTTPStatus.UNAUTHORIZED assert response.text == "Authorization failed." def test_handle_post_with_valid_credentials(mocker, client): """ Given: a server that expects a user and password When: Calling post with proper credentials Then:a 200 response code is recieved """ mocker.patch.object(demisto, "params", return_value={"credentials": {"identifier": "user", "password": "pass"}}) response = client.post("/", json=[{"name": "Test Incident"}], auth=("user", "pass")) assert response.status_code == HTTPStatus.OK assert response.text == "[]" def test_handle_post_with_missing_data(mocker, client): """ Given: A request with no body When: Post is called Then: A readable error message is returned """ mocker.patch.object(demisto, "error") response = client.post("/") assert response.status_code == HTTPStatus.BAD_REQUEST assert "Request, and rawJson field if exists must be in JSON format" in response.text def test_handle_post_with_invalid_json(mocker, client): """ Given: A request with a bad body When: Post is called Then: A readable error message is returned """ mocker.patch.object(demisto, "error") response = client.post("/", data="invalid_json") assert response.status_code == HTTPStatus.BAD_REQUEST assert "Request, and rawJson field if exists must be in JSON format" in response.text @pytest.mark.parametrize( "body", [ {"name": "Test Incident 1", "type": "Test Type 1", "occurred": "2024-03-17T12:00:00Z", "rawJson": {"key": "value"}}, {"name": "Test Incident 1", "type": "Test Type 1", "occurred": "2024-03-17T12:00:00Z", "key": "value"}, {"name": "Test Incident 1", "type": "Test Type 1", "occurred": "2024-03-17T12:00:00Z", "raw_json": {"key": "value"}}, ], ) def test_parse_request(body): """ Given: two inputs, either with raw_json being real json or a string representation of json When: calling parse_body Then: The body is parsed the same """ # Prepare a mock Request object with JSON data mock_request = MagicMock(spec=Request) async def mockbody(): return body mock_request.json = mockbody # Call the parse_incidents function with the mock Request result = asyncio.run(parse_incidents(mock_request)) # Check if the function returns a list of dictionaries with the parsed incidents assert isinstance(result, list) assert len(result) == 1 assert result[0]["name"] == "Test Incident 1" assert result[0]["type"] == "Test Type 1" assert result[0]["occurred"] == "2024-03-17T12:00:00Z" assert result[0]["rawJson"]["key"] == "value" def test_main_test_module(mocker): mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "params", return_value={"longRunningPort": "444"}) results = mocker.patch.object(demisto, "results") main() assert results.call_args_list[0].args[0] == "ok" def test_main_long_running(mocker): """ We have an autorecovery mechanism here that when the app fails with an exception it should be restarted five seconds later """ mocker.patch.object(demisto, "error") mocker.patch.object(demisto, "command", return_value="long-running-execution") mocker.patch.object( demisto, "params", return_value={"longRunningPort": "444", "certificate": "something", "key": "something"} ) mocker.patch.object(demisto, "results") mocker.patch("time.sleep") uvicornmock = MagicMock(side_effect=[Exception("restart once"), Exception("Twice"), BaseException("Hack to get out")]) mocker.patch("uvicorn.run", uvicornmock) try: main() raise AssertionError except BaseException: """ This is kind of a hack to stop the while true loop""" assert len(uvicornmock.call_args_list) == 3