Skip to main content

Webhooks

With webhooks you can create REST endpoints which create Cloudomation executions when called.

Use Cases

Use webhooks to

  • start Cloudomation executions from third party systems
  • receive notifications from third party systems
  • receive callbacks from asynchrounous processes running in third party systems
  • create custom status HTML pages which display information gathered from Cloudomation
  • expose information or functionality to consumers which do not have a Cloudomation user

Concept

As long as an enabled webhook exists in Cloudomation the REST endpoint is available to receive calls.

webhooksInternetInternetWebhookWebhookInternet->WebhookExecutionExecutionWebhook->ExecutionIntranetIntranetIntranet->Webhook
note

Each call to a productive webhook will consume one connection from your connection allowance.

HTTP method

The endpoint accepts the HTTP methods GET, DELETE, HEAD, POST, PUT, and PATCH. The method which was used is passed to the execution. If the HTTP method allows for a body payload and a body payload was provided, it is also passed to the execution.

danger

A HTTP GET request should only be used to retrieve a resource, while a HTTP POST request should be used to create a resource. Calling a Cloudomation webhook creates an execution. Thus the recommended way to call a webhook is by using a POST request. For compatibility reasons Cloudomation also allows calling webhooks using a GET request.

example

Consider a webhook named "captain" in the workspace "jollyroger".

curl https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
# the execution will receive in the input_value:
# {"method": "GET"}

curl -X PUT https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
# the execution will receive in the input_value:
# {"method": "PUT"}

curl -X DELETE https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
# the execution will receive in the input_value:
# {"method": "DELETE"}

Path

Each webhook creates a base URL where it can be called. All calls below that base URL are also accepted. The path which was used is also passed to the execution.

example

Consider a webhook named "captain" in the workspace "jollyroger".

curl https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
# the execution will receive in the input_value:
# {"path": None}

curl https://jollyroger.cloudomation.com/api/latest/webhook/captain/call/
# the execution will receive in the input_value:
# {"path": ""}

curl https://jollyroger.cloudomation.com/api/latest/webhook/captain/call/some/path
# the execution will receive in the input_value:
# {"path": "some/path"}

Headers

All HTTP headers which are sent from the client are passed to the execution.

example

Consider a webhook named "captain" in the workspace "jollyroger".

curl https://jollyroger.cloudomation.com/api/latest/webhook/captain/call -H "my-header: value"
# the execution will receive in the input_value:
# {"headers": {"my-header": "value"}}
note

Curl also sets other headers which are passed to the execution. For brevity they were omitted from the example above.

Query parameters

All query parameters which are used are passed to the execution.

example

Consider a webhook named "captain" in the workspace "jollyroger".

curl https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
# the execution will receive in the input_value:
# {"query": {}}

curl https://jollyroger.cloudomation.com/api/latest/webhook/captain/call?mode=search&q=foo
# the execution will receive in the input_value:
# {"query": {"mode": "search", "q": "foo"}}

Configuration

Please see the table below for the different webhook fields and their meanings.

FieldDescription
FlowThe flow which is started when the webhook is called
EnabledIf unset, the HTTP endpoint is not available
Is productiveIf set, the executions are started in productive mode. See Development and Productive Mode.
Require login token cookieIf set, the client has to provide a valid Cloudomation login token for the call to succeed. See Authentication. The execution will additionaly receive auth in the input_value containing information about the Cloudomation user.
KeyAn API key. If set, the client has to provide the API key for the call to succeed. The API key can be specified as a query parameter or as a key in a JSON payload.
URLA preview of the base URL of the webhook (Readonly)
note

When a new webhook is created, a random API key will be generated for you.

note

A webhook in development-mode will immediately return with HTTP 402 when no user was active in the Cloudomation UI within the last 10 minutes.

Supported content-types

Cloudomation webhooks support the content-types application/json or application/yaml. If no request body is used, the value of the "Content-Type" header is ignored.

Public webhooks

A webhook can be considered public when it requires neither a login token nor an API key. For cloud workspaces, a public webhook can be called by anybody on the internet. For on-premise installations, public webhooks can be called by anybody who has network access to your Cloudomation workspace.

danger

Be considerate and careful when exposing a public webhook. Always validate untrusted user input!

Private webhooks

A webhook can be considered private when it requires a login token and/or an API key.

example

Consider a webhook named "captain" in the workspace "jollyroger" which requires a login token.

# call without a token
$ curl -i https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
HTTP/1.1 401 Unauthorized
...

"401 Unauthorized"

# call with a token
$ curl -i -H "x-cloudomation-token: ey..." https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
HTTP/1.1 200 OK
example

Consider a webhook named "captain" in the workspace "jollyroger" which requires an API key.

# call without API key
$ curl -i https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
HTTP/1.1 401 Unauthorized

# call with API key in the headers
$ curl -i -H "Authorization: Bearer my-secret-key" https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
HTTP/1.1 200 OK

# call with API key in query parameter
$ curl -i https://jollyroger.cloudomation.com/api/latest/webhook/captain/call\?key=my-secret-key
HTTP/1.1 200 OK

# when using a method which allows for a body payload
# the API key can also be specified in the JSON body
$ curl -i -H "Content-Type: application/json" -d '{"key": "my-secret-key"}' -X POST https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
HTTP/1.1 200 OK
note

The value of the API key will not be passed to the execution.

note

If the API key is specified in multiple places, they will be applied in the order: body payload -> query parameter -> authentication header

Synchronous and asynchronous webhooks

Per default webhooks are called synchronously. The HTTP call will block until the execution reaches the ENDED_SUCCESS status. In synchronous mode the output_value of the execution will be returned to the client in the response payload as JSON.

It is possible to call a webhook asynchronously by adding async to the query parameters. In asynchronous mode the HTTP response code will be 201 (Created) and the ID of the created execution will be returned in the response body.

example

Consider a webhook named "captain" in the workspace "jollyroger".

# synchronous mode, the output_value is returned as JSON
$ curl -i https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
...

{"output": "value", "returned": "here"}

# asynchronous mode, the execution ID is returned as plaintext
$ curl -i https://jollyroger.cloudomation.com/api/latest/webhook/captain/call\?async
HTTP/1.1 201 Created
Content-Type: text/plain; charset=utf-8
...

aeae71f4-b062-4655-a333-d4d58014cf64
tip

Do not use synchronous webhooks for long-running executions. In most cases a HTTP timeout will interrupt your query. The execution will continue running though.

Error handling

When the execution does not end with ENDED_SUCCESS Cloudomation will return the HTTP status 500 (Internal Server Error). The status and the status message of the execution will be returned in the response body.

example

Consider a webhook named "captain" in the workspace "jollyroger".

$ curl -i https://jollyroger.cloudomation.com/api/latest/webhook/captain/call
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
...

ENDED_ERROR: status-message which was set by the execution

Custom responses

You can return custom responses from webhooks. There are three helper methods available:

example

Use of a custom response to perform a redirect

The helper webhook_response allows for customization of status, headers, and body:

import flow_api

def handler(system: flow_api.System, this: flow_api.Execution):
return this.webhook_response(
status=302, # the HTTP status code to return
headers={ # Additional HTTP headers to return
'Location': 'https://cloudomation.com',
},
body='response-body', # The body payload to return
)

The helper webhook_html_response accepts a HTML string and will set the status to HTTP 200 and content-type to text/html:

example

Use of a custom HTML response

import random
import flow_api

def handler(system: flow_api.System, this: flow_api.Execution):
return this.webhook_html_response(
body=(
f'''
<h1>Welcome to my homepage</h1>
<p>Rolling the dice: {random.randint(1,6)}</p>
<a href="call">roll again</a>
'''
),
)

The helper webhook_json_response accepts any object which can be JSON serialized and will set the status to HTTP 200 and content-type to application/json:

example

Use of a custom JSON response

import flow_api

def handler(system: flow_api.System, this: flow_api.Execution):
return this.webhook_json_response(
body={
"some": [
"nested",
"object",
],
},
)

CRUD Example

The following is a simple flow script which implements a REST API for a food store. It shows how to read the path of the endpoint and return a custom body and HTTP status.

import json
import flow_api

def handler(system: flow_api.System, this: flow_api.Execution):
input = this.get('input_value')
storage_setting = system.setting("food store")
try:
storage_setting.get("value")
except flow_api.exceptions.ResourceNotFoundError:
storage_setting.save(
value=[
{
"name": "apple",
"price": 1,
},
{
"name": "banana",
"price": 2,
},
]
)

storage_setting.acquire()
storage = storage_setting.get("value")

status = None
body = None
if input['method'] == 'GET':
if not input['path']:
status = 200
body = storage
else:
for item in storage:
if item["name"] == input["path"]:
status = 200
body = item
break
elif input["method"] == 'POST':
item = {
"name": input["json"]["name"],
"price": input["json"]["price"],
}
storage.append([item])
storage_setting.save(value=storage)
body = item
status = 201
elif input["method"] == 'DELETE':
storage = [x for x in storage if x["name"] != input["path"]]
storage_setting.save(value=storage)
body = ""
status = 200
elif input["method"] == 'PATCH':
for item in storage:
if item["name"] == input["path"]:
item["name"] = input["json"].get("name", item["name"])
item["price"] = input["json"].get("price", item["price"])
storage_setting.save(value=storage)
body = item
status = 200
break

return this.webhook_response(
status=status,
body=json.dumps(body),
)
example

List all items in the food store

curl -X GET https://jollyroger.cloudomation.com/api/latest/webhook/food/call
[{"name": "banana", "price": 2}, {"name": "apple", "price": 1}]
example

Get the food with name "apple"

curl -X GET https://jollyroger.cloudomation.com/api/latest/webhook/food/call/apple
{"name": "apple", "price": 1}
example

Delete a food item.

curl -X DELETE https://jollyroger.cloudomation.com/api/latest/webhook/food/call/apple
example

Create a new food item

curl -X POST https://jollyroger.cloudomation.com/api/latest/webhook/food/call -d '{"name": "apple", "price": 2}' -H 'Content-Type: application/json'
{"name": "apple", "price": 2}
example

Update an existing item

curl -X PATCH https://jollyroger.cloudomation.com/api/latest/webhook/food/call/apple -d '{"price": 1}' -H 'Content-Type: application/json'
{"name": "apple", "price": 1}