Launching webhook triggers with Python
While webhooks can be set up from outside services, you can also create custom workflows by launching webhook triggers yourself. This reference is focused on webhook triggers on Valohai, but you can also use these tools to launch webhooks at other services on the Internet that accept them.
Authorization
While it is not strictly necessary to add authorization headers to secure your webhooks, it is very much recommended. All examples provided here implement authorization conditions.
You can use the demonstration trigger from the Launch Pipelines with Webhooks chapter as a base to experiment with.
Secrets Management
Authorization requires managing secrets to verify requests with. The following examples assume we have a
secret value set in the project environment variables called WEBHOOK_TOKEN
. Create it as follows:
- Generate a reasonably long secret passphrase or random text. You can use your password manager to do this.
- On the Valohai app, go to Project settings -> Env variables
- On the bottom of the list, enter
WEBHOOK_TOKEN
as variable name, and the secret created in step (1) to the variable value. - Check the key icon and save.
Token as an environment variable
If you are running the examples on a local machine or otherwise outside Valohai, you should add the token to your running environment for these examples to work. On a Valohai execution within your project, the environment variable is available automatically.
You can add an environment variable to your shell with export WEBHOOK_TOKEN=<secret>
. When you start another
shell, you will need to do it again.
Creating Webhook Triggers
Webhook triggers can be created in the project settings, under Triggers. Click “Create Trigger” and select “Webhook” as the trigger type.
Trigger behaviour can be defined with conditions that validate the webhook request and actions that can launch specific executions and pipelines in response to a valid request.
Webhook conditions reference
As with Valohai triggers in general, all conditions must simultaneously be satisfied for the trigger to launch.
Failed conditions result in a 400 Bad Request
response code to the webhook, and a log entry may be recorded on the
trigger log.
Web Authentication condition
This is the main tool to authenticate webhook requests with several flexible options.
Auth type and Secret key: The token’s internal format and its verification data.
- Static secret token: Secret key is a sequence of characters the token is compared to.
- Hash-based Message Authentication Code (HMAC): Secret Key is a shared secret with the sender that is used to generate the HMAC signature.
- JSON Web Token (JWT): Secret key is either a symmetric key or the asymmetric public key that verifies the given JWT-format token.
Verifying the request body
HMAC can only use Header and Querystring lookup namespaces to get the HMAC signature to compare against, as it cannot generate a signature from data that contains the signature itself.
The secret key can use (secret) project environment variables using {env:MY_ENV_VAR}
. This is very useful
if you have several webhooks from the same provider reusing the same verification secret.
HMAC Basestring Format: HMAC only. This describes the structure of the HMAC signature’s source data, which
can use the request body content as well as select headers from the request. You can include the entire
request body with {body}
. You can include request header values with {header:My-Header}
. If the named header is
not found in the request, the condition will always fail. This field is typically used to represent a static prefix,
punctuation or timestamp value in the HMAC signature.
The remaining options describe how the token to compare against is acquired.
Token Lookup Namespace: Where to look for the authentication token?
- HTTP Header: Supplied within the request headers. Key is the header name in standard
X-Http-Header
format. - Querystring: Supplied as part of the URL after the question mark:
https://app.valohai.com/api/v0/launch/.../?search=sharks&size=3
would have the querystring keysearch
with the valuesharks
, and the keysize
with the value3
. - URL Encoded Body: Supplied as part of the request body content, in the same format as querystring
- JSON Body: Supplied as part of the request body content, using JSON format. Note! Only top level keys are supported.
Token Lookup Key: What part of the namespace contains the authentication token?
Value Prefix: The token often has a static prefix that is not part of the token. This field can be used to cut the prefix away prior to verifying the remaining token.
Static token authorization example
Static token authorization is handled with a Web Request Authentication trigger condition. This is the simplest authorization method as a shared, static secret is passed by the webhook sender within the request and compared with the stored secret on the Valohai project.
Add a static token authentication condition with these steps:
- For Auth Type, select “Static Secret Token”
- For Token Lookup Namespace, select “HTTP Header”
- For Token Lookup Key, type
Authorization
- For Secret Key, enter
{env:WEBHOOK_TOKEN}
- Leave Value prefix empty.
Launch a webhook using urllib
(no install required):
import os
import urllib.request
import urllib.parse
urllib.request.urlopen(urllib.request.Request(
'<paste the webhook URL here>',
data=urllib.parse.urlencode({
'hello': 'valohai',
'prompt': 'draw me a shark'
}).encode(),
headers={
'Authorization': os.getenv("WEBHOOK_TOKEN")
}
))
Launch a webhook with requests
(install it with pip install requests
or add requests
to your requirements.txt):
import os
import requests
requests.post(
'<paste the webhook URL here>',
data={'hello': 'valohai', 'prompt': 'draw me a shark'},
headers={'Authorization': os.getenv("WEBHOOK_TOKEN")}
)
Requests and Urllib
Read more about how these libraries work on their respective documentation pages: Requests and Urllib
Hash-based Message Authentication Code (HMAC) authentication example
Hash-based Message Authentication Code (HMAC) uses the shared secret to create a check value derived from the request’s contents, which is not a secret. The check value is then generated again by the condition and compared with the value sent with the request to validate it. HMAC is more secure than a simple shared token, as the token is never transmitted within the request.
Add a HMAC authentication condition with these steps:
- For Auth Type, select “Hash-based Message Authentication Code”
- For Token Lookup Namespace, select “Header”
- For Token Lookup Key, type
Authorization
- For Secret Key, enter
{env:WEBHOOK_TOKEN}
- Leave Value prefix empty.
- For Algorithm, select
SHA256
Launch the trigger with this example Python code:
import hmac
import os
import urllib.request
import urllib.parse
data = urllib.parse.urlencode({
'hello': 'valohai',
'prompt': 'draw me a shark'
}).encode()
calculated_hmac = hmac.new(
key=os.getenv("WEBHOOK_TOKEN").encode(),
msg=data,
digestmod='sha256',
).hexdigest()
urllib.request.urlopen(urllib.request.Request(
'<paste the webhook URL here>',
data=data,
headers={
'Authorization': calculated_hmac
}
))
JSON Web Token (JWT) authentication example
JSON Web Tokens (JWT) use a shared or asymmetric secret to sign a token authenticating the request. A new, quickly expiring token can be generated for each webhook request. Like HMAC, the shared secret is not transmitted with the request.
Asymmetric algorithms (e.g. RS256
) can be used to avoid sharing secrets. To do so, create an
RSA keypair (private and public key). Encode the JWT with the private key and set
the WEBHOOK_TOKEN
to the corresponding public key. The public key only
allows verifying that the token was created by the private key. This example uses a symmetric
algorithm.
Add a JWT authentication condition with these steps:
- For Auth Type, select “JSON Web Token”
- For Token Lookup Namespace, select “HTTP Header”
- For Token Lookup Key, type
Authorization
- For Secret Key, enter
{env:WEBHOOK_TOKEN}
- Leave Value prefix empty.
- For Algorithm, select
HS256
Launch the trigger with this example Python code. You will need PyJWT installed to run this, which you
can get with e.g. pip install pyjwt
.
import jwt
import os
import time
import urllib.request
import urllib.parse
data = urllib.parse.urlencode({
'hello': 'valohai',
'prompt': 'draw me a shark'
}).encode()
token = jwt.encode(
{'ts': int(time.time())},
key=os.getenv("WEBHOOK_TOKEN"),
headers={'exp': int(time.time() + 60)}
)
urllib.request.urlopen(urllib.request.Request(
'<paste the webhook URL here>',
data=data,
headers={
'Authorization': token
}
))
More Web Authentication condition examples
Here are some more examples to help match webhook authorization headers with Valohai trigger conditions. You can inspect how webhook requests look like by configuring them to deliver to a custom Web server or a service like Smee. Valohai does not provide direct access to Webhook request contents as they may contain sensitive information.
Incoming header: Authorization: JWT ey...(JWT)
- Auth type: JWT
- Token lookup namespace: HTTP Header
- Token lookup key:
Authorization
- Value prefix:
JWT
. Note! Remember the trailing space. The prefix is cut from the beginning of the supplied value exactly. - Secret key: Secret key that verifies the JWT
Incoming header: X-Hmac-Signature: v0=u2jhlpq...(HMAC signature)
The HMAC comparison is specified to use the static string “hmac”, a value from another header “X-Hmac-Timestamp” and the request body, delimited with the colon character “:”.
- Auth type: HMAC
- Token lookup namespace: HTTP Header
- Token lookup key:
X-Hmac-Signature
- Value prefix:
v0=
- HMAC Basestring Format:
hmac:{header:X-Hmac-Timestamp}:{body}
- Secret key: Shared secret with the sender
Incoming request content: token=pat_842717_...(static token)&event_type=new_data&...
- Auth type: Static token
- Token lookup namespace: URL Encoded body
- Token lookup key: “token”
- Secret key: “pat_842717_…(static token)”
Web Timestamp condition
Timestamp conditions are typically used combined with HMAC authentication conditions to make HMAC-signed requests expire after a short period, which protects against replay attacks where a valid request is recorded and sent again later.
Timestamp lookup namespace and lookup key work the same way as web authentication.
If the timestamp cannot be found, or is found but is more than Drift tolerance seconds away from current time, the condition fails.
The timestamp must be a UNIX timestamp as seconds.
Web Timestamp with HMAC authentication example
Add a Web Request Authentication condition using HMAC with these steps:
- For Auth Type, select “Hash-based Message Authentication Code”
- For Token Lookup Namespace, select “HTTP Header”
- For Token Lookup Key, type
Authorization
- For Secret Key, enter
{env:WEBHOOK_TOKEN}
- For HMAC Basestring Format, type
prefix.{body}.{header:Request-Timestamp}
- Leave Value prefix empty.
- For Algorithm, select “SHA256”
Add a Web Request Timestamp condition with these steps:
- For Token Lookup Namespace, select “HTTP Header”
- For Token Lookup Key, type
Request-Timestamp
- For Tolerance, type
60
(this is in seconds)
Launch this trigger with this example Python code:
import time
import hmac
import os
import urllib.request
import urllib.parse
data = urllib.parse.urlencode({
'hello': 'valohai',
'prompt': 'draw me a shark'
}).encode()
timestamp = int(time.time())
calculated_hmac = hmac.new(
key=os.getenv("WEBHOOK_TOKEN").encode(),
msg="prefix.{body}.{timestamp}".format(body=data, timestamp=timestamp),
digestmod='sha256',
).hexdigest()
urllib.request.urlopen(urllib.request.Request(
'<paste the webhook URL here>',
data=data,
headers={
'Authorization': calculated_hmac,
'Request-Timestamp': timestamp,
}
))
Rate Limit condition
Rate limit conditions can prevent triggers from launching more often than you intend. You should set a reasonable rate limit to protect against accidentally launching too many triggered workloads.
Set a time Period (in seconds) during which at most Quota of triggers can be launched. This condition fails if the trigger being launched would exceed the quota.
Several rate limits can be applied at once to allow e.g. a large daily quota, but also a smaller hourly quota that you can’t use the whole day’s quota at once.
Webhook Actions Reference
Web Response Text & JSON
These actions can customize successful webhook responses. They can only show static content. Failed response content
cannot be customized. The default response is ok
(text/plain
). Only the first web response action
will be used.
For Response Text, the content type will be text/plain
.
For Response JSON, the content type will be application/json
.
Run Execution
Webhook-specific parameter Payload input name allows setting the webhook request body as an input on the execution being launched. The execution step must have a matching declared input in the YAML.
The execution will receive the untouched request body into that input when launched through the webhook, and is responsible for parsing and processing it.
Run Pipeline
Webhook-specific parameter Payload input name allows setting the webhook request body as an
input on one of the executions in the pipeline being launched. The format is nodename.input_name
.
The node of the specified name must be an execution node and the node’s execution step must have matching a
declared input in the YAML.
Multiple nodes with webhook input?
While only one execution node can directly receive the webhook request body as an input, the pipeline can define input-input edges to distribute it to any other execution nodes as needed.