- WebHookShell
In the modern world, there are different approaches to running scripts using WebHooks, such as Azure Functions, Azure Automation, or Amazon Lambda. In the on-prem world, users might use tools like Jenkins merely to provide the ability to trigger function execution, which can be overkill for webhooks. This small, lightweight project was born as an open-source alternative that you can easily deploy on both Linux and Windows machines.
Users send an HTTP GET or POST message to the server. The call includes the following parameters:
- Security Key
- Script name
- [Optional] Param
The project has swagger installed. Just open http[s]://{YourAddress}:{Port}.
The base project URI is:
https://localhost:5001/webhook/v1?key=yourKey&script=YourScript¶m=-Your-Params
To protect scripts from accidental or unauthorized executions, the server will load the Key from appsettings.json and compare it with the one from the user request. If they don't match, the server will stop executing the pipeline and return an error:
If any exceptions arise along the way, the global exception handler will return an error:
Here is an example of a successful webhook without params:
https://localhost:5001/webhook/v1?key=24ffc5be-7dd8-479f-898e-27169bf23e7f&script=Test-Script.ps1
And with parameters:
https://localhost:5001/webhook/v1?key=24ffc5be-7dd8-479f-898e-27169bf23e7f&script=Test-Script.ps1¶m=-Param1-A-Param2-B
This example is different only in terms how you supply the data.
Here you can see that the http body is used.

Optionally you can specify a trigger for your script. The trigger acts as a filter or the set of rules for the script. Available triggers:
- httpMethod
- ipAddresses
- timeFrame
You can set certain script to be executed only from POST, GET or from both methods.
This parameter is optional. If not set then you can run this script from both methods.
Add IP address to the array if you need to restrict the script to run only from the certain caller IP address. Both IPv4 and IPv6 are supported. Optional parameter. If not provided then any IP address can call the script.
You can specify one or more time frames when you have a time constrain.
If set then the script can be launched only on specified time range[s].
Time format HH:mm:ss in UTC.
Optional parameter. If not set then the script can be launched anytime of the day.
In this example script test-script.py can be executed only from GET method and only if the caller is in the ipAddresses list.
"ScriptsMapping": [
{
"name": "test-script.py",
"key": "77aae8aa-50d2-49d9-be8c-e9f59aaf39e9",
"trigger": {
"httpMethods": ["GET"],
"ipAddresses": [
"127.0.0.1",
"::1"
],
"timeFrames":
[
{
"startUtc": "03:00:00",
"endUtc": "07:59:59"
},
{
"startUtc": "23:00:00",
"endUtc": "00:59:59"
}
]
}
}
]You can find two default script handlers in the configuration:
- Python3
- Pwsh (Powershell)
If you keep them as they are, you need to install pwsh and python and add them to the PATH variable (default for Windows) on the server where this API will be running.
There are a few places where you can define the security key:
- You can provide a key for each script. In this case, you don't need to share a common one.
- Per script handler. All scripts registered under the handler will share the same key if they don't have a unique key (check option 1).
- Global key (or Default). This key will be used
if the script or handler don't have keys specified.
For example:
"Scripts": {
"DefaultKey": "24ffc5be-7dd8-479f-898e-27169bf23e7f",
"Handlers": [
{
"ProcessName": "pwsh",
"ScriptsLocation": "./powershellscripts",
"FileExtension": "ps1",
"Key": "88aae8aa-50d2-49d9-be8c-e9f59aaf3988"
},
{
"ProcessName": "python3",
"ScriptsLocation": "./pythonscripts",
"FileExtension": "py",
"ScriptsMapping": [
{
"name": "test-script.py",
"key": "77aae8aa-50d2-49d9-be8c-e9f59aaf39e9",
"trigger": {
"httpMethods": ["GET"],
"ipAddresses": [
"127.0.0.1",
"::1"
]
}
}
]
}
]
}In this example, the script test-script.py has a unique key 77aae8aa-50d2-49d9-be8c-e9f59aaf39e9. The pwsh handler has a key that will be valid for all scripts that don't have a unique key set.
This app was built as a cross-platform and can be installed on many well-known platforms such as Mac, Windows, and Linux. There are multiple ways of running this app on the server, but let's concentrate on two major approaches.
Pull the docker image from the repository.
Note: This container was built and pushed to the dockerHub using
linux/amd64. Please create an issue in this repository and I will build and publish for the other platforms when I have time.
docker pull mtokarevv/webhookshell:latest
docker run --platform linux/amd64 -d -p 8080:8080 webhookshell
To check if it works, please navigate to http://YourAddress:8080. It should open the Swagger sandbox, where you can run test scripts.
This container can and should be run behind HTTPS:
docker run --platform linux/amd64 -d -p 8080:8080 -p 8443:8443 -v /Users/mtokarev/Desktop:/certs:ro -e ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/certificate.pfx -e ASPNETCORE_Kestrel__Certificates__Default__Password=123 webhookshell
| Option | Explanation |
|---|---|
-v /Users/mtokarev/Desktop:/certs:ro |
This mounts the local directory /Users/mtokarev/Desktop to the /certs directory in the container as read-only (ro). In this example this directory has a certificate certificate.pfx. |
-e ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/certificate.pfx |
This sets an environment variable to specify the path to the SSL certificate (certificate.pfx) inside the container. |
-e ASPNETCORE_Kestrel__Certificates__Default__Password=123 |
This sets an environment variable to specify the password for the SSL certificate (certificate.pfx). |
You can also bind a script folder from the host if you want by using -v when you run the container.
Make sure you have git and docker installed.
git clone https://github.com/MTokarev/webhookshell.git && cd webhookshell
docker build -t webhookshell .
docker run -d -p 8080:8080 webhookshell
This repository no longer publishes releases except docker images. Please build project by yourself if needed or consider run it as a docker container.
The biggest advantage of the framework-dependent version is a smaller app footprint. This app does not include a runtime and hence is lightweight, consuming only 100-200KB of storage. It is also easier to distribute because the same executable can be run on different platforms.
To use this approach, you need to install the .NET 6.0 runtime on the server. You can download and follow the steps outlined on this page.
This is a self-contained package that includes a runtime and can be executed on the server without installing dependencies. Because it includes a runtime that is specific to the operating system (and architecture), you cannot run a Windows build on Linux and vice versa.
Also, the build will be heavier than the framework-dependent version.
I have a TODO item to simplify the release process and publish binaries for all platforms together. Currently, you can find published binaries here.
Each platform will have a dedicated release. For example, Release 14 will include:
- Release 14 for Windows
- Release 14 for RedHat
- Release 14 for Linux
- Release 14 (Requires .NET 6.0 runtime)
I strongly recommend setting up an HTTPS listener and keeping it as the only available entry point.
You can run this API as a service or put it behind the proxy/web server. For instance IIS, Apache, NGINX.
By default, I've included one ps1 script Test-Script.ps1 that you can run. The script has 3 optional parameters:
-Param1 [string]
-Param2 [string]
If you provide -Param1 A -Param2 B as parameters, the script will return them (as shown in the screenshot above).
Test it using swagger
By default, the web app on Windows platforms uses the applicationPool context (LocalSystem), which means that it has local admin access. You can provide any credentials inside your ps1 script by using your own logic, or you can change the appSetting username in IIS to change the context.
On linux it is up to you to choose the user context.
When you need to add a new script handler, you just need to modify appsettings.json. You don't need to change the code




