> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getoperate.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Webhooks are a way to interact with getOperate using standard web technologies.

<Tip>
  Snippet of a Deno/TypeScript script using only native JavaScript to trigger a
  webhook can be found on [getOperate Hub](#).

  Some use cases include triggering scripts and flows from [Slack](#) or [Emails](#).
</Tip>

Each Script and Flow created in getOperate gets autogenerated webhooks. The webhooks depend on how they are triggered, and what their return values are.

## Addresses

Webhook links can be found on the Detail page of each Script and Flow. For
more information about
[Scripts](#)
or [Flows](#), refer to the
Getting Started section.

Each webhook has two URLs, one with the path to the script, i.e.
`/p/u/<your_user>/<your_script_name>`, which will always trigger the latest
version of the Script/Flow and the other one with just a hash, i.e. `/h/<hash>`,
hiding potentially sensitive information and always corresponding to that
version of the script, even with overwrites.

### Asynchronous

Jobs can be triggered in asynchronous mode, meaning that the webhook is triggered,
and the returning value is the uuid of the job assigned to execute the underlying code

These links are available in the "UUID/Async" tab.

### Synchronous

The second type of autogenerated endpoint is the **synchronous** webhook. This
webhook triggers the execution, automatically extracts the underlying code's
return value and returns it as the response.

Every script exposes an endpoint that triggers the Script but waits for its
full execution before returning.

The endpoint has the following format:

```bash theme={null}
https://app.getOperate.dev/api/w/$WORKSPACE_ID/jobs/run_wait_result/$SCRIPT_PATH
```

where \$SCRIPT\_PATH is the path of the Script in the workspace, including the prefix `u/` or `f/`

These links are available in the "Result/Sync" tab.

Sync get endpoints support adding args directly, use `include_query=a,b,c&a=foo&b=bar&c=foobar` to have a: "foo", b: "bar", c: "foobar", passed as args.

<Note>
  For scripts, there is an additional **synchronous** webhook available that accepts a `GET` request.
  The payload must be passed as the query arg `payload` and encoded in JSON first, then in an URL safe base64,
  e.g: `encodeURIComponent(btoa(JSON.stringify({a: 2})))`.
  This endpoint has the same URL as the Result/Sync POST Path URLs.
</Note>

<Warning>
  Be cautious with potentially long-running jobs in **synchronous** mode.
</Warning>

### Asynchronous vs. Synchronous

It's always better to use asynchronous mode as it allows your client not to wait for the response and it avoids getOperate to have to maintain a connection to your client while the job is running. However, for short-running jobs where it's easier in your code to block until you get a response, then use the synchronous mode.

When using the **synchronous mode**, the webhook returns the result of the script directly. If the script returns an error, the endpoint still returns the `200` status code with the error as a JSON object.

When using the **asynchronous mode**, the webhook returns a `uuid` and you can poll the [get job](#) API call to fetch the status and results once it is completed.

## User token

To interact with getOperate you always need to use `Bearer` token authentication.

You can generate tokens for your own account in the
**Account Settings menu** in the app. Open it by
clicking your username on the side menu, then select "Account settings".

Labels are only used to allow users to easily distinguish keys.

<Warning>
  You can only see the token once, when it's created. Make sure to store it
  securely!
</Warning>

## Webhook specific tokens

Webhook specific tokens allow sharing tokens publicly without fear since the token will only be able to trigger a specific script/flow and not impersonate you for any other operations.

It also avoids the hassle of having to create an anonymous user and check their permissions. If you can run the script yourself, then the webhook specific token will still inherit your own permissions.

## Triggering

Once you have a webhook URL and a user token, issue a request to the
endpoint and you will get the appropriate return as response.

The bearer token must be passed as either an `Authorization: Bearer <TOKEN>`
header, or as a `token` query parameter:
`https://<instance>/<route>?token=<TOKEN>`

<Warning>
  Due to security reasons, it is highly recommended to pass the token in the
  header. If it's not possible, then URL that contains the token should be treated
  as a secret (for more context please check [OWASP ref.1](#) and [OWASP ref.2](#)).
</Warning>

Examples using cURL for `POST` requests:

```bash theme={null}
# Request with Header
curl -X POST \
    --data '{}'                            \
    -H "Content-Type: application/json"    \
    -H "Authorization: Bearer supersecret" \
    ".../w/demo/jobs/run_wait_result/p/u/bot/hello_world_deno"
```

```bash theme={null}
# Query parameter
curl -X POST                               \
    --data '{}'                            \
    -H "Content-Type: application/json"    \
    ".../w/demo/jobs/run_wait_result/p/u/bot/hello_world_deno?token=supersecret"
```

Examples using cURL for synchronous GET requests:

```bash theme={null}
# Request with Header".
curl -X GET \
    -H "Content-Type: application/json"    \
    -H "Authorization: Bearer supersecret" \
    ".../w/demo/jobs/run_wait_result/p/u/bot/hello_world_deno?payload=<URL_SAFE_BASE64_ENCODED_JSON>"
```

You can find an example using only standard Deno libraries on the
[getOperate Hub](#).

You can also verify that the job has been triggered and run (or investigate any
encountered issues), by checking the [Runs menu](#) on the app.

## Request headers

It is possible for jobs to take request headers as arguments. To do so, either specify in the query args the headers to process at `include_header`, separated with `,`. e.g: `/api/w/admins/jobs/run_wait_result/p/u/user/undisputed_script?include_header=X-Sign,foo`

or use the env variable: `INCLUDE_HEADERS` with the same format so that all requests to any job will include the headers.

## Non Object payload / body

If the payload is not an object, it will be wrapped in an object with the key `body` and the value will be the payload/body itself. e.g:

```
[1,2,3] => {"body": [1,2,3]}
```

and your script can process it as:

```python theme={null}
def main(body: List[int]):
    print(body)
```

## Raw payload / body

Similarly to request headers, if the query args contain `raw=true`, then an additional argument will be added: `raw_string` which contains the entire json payload as a string (without any parsing). This is useful to verify the signature of the payload for example (discord require the endpoints to verify the signature for instance).

## Custom Response Code

For all sync run jobs endpoints, if the response contains a key `getOperate_status_code` with a number value, that value will be used as the status code. For example, if a script or flow returns:

```json theme={null}
{
	"getOperate_status_code": 201,
	"result": {
		"Hello": "World"
	}
}
```

the synchronous endpoint will return:

```json theme={null}
{
	"Hello": "World"
}
```

with a status code `201`.

Note that if the status code is invalid (w\.r.t [RFC9110](https://httpwg.org/specs/rfc9110.html#overview.of.status.codes)), the endpoint will return an error.

## Custom Content Type

Similarly to the above, for all sync run jobs endpoints, if the response contains a key `getOperate_content_type`, the associated value will be used as the content type header of the response. For example, if a script or flow returns:

```json theme={null}
{
	"getOperate_content_type": "text/csv",
	"result": "Hello;World"
}
```

the synchronous endpoint will return:

```csv theme={null}
"Hello;World"
```

with the response header: "Content-Type: text/csv".

## Return early for flows

It is possible to define a node at which the flow will return at for sync endpoints. The rest of the flow will continue asynchronously.

Useful when some webhooks need to return extremely fast but not just the uuid (define first step as [early stop](#)) or when the expected return from the webhook doesn't need to the full flow being computed.

<Card title="Return Early for Flows">
  Define a node at which the flow will return at for sync endpoints.
</Card>

## Exposing a webhook URL

Single port proxy can be leveraged to expose a webhook with a custom URL. In its docker-compose, getOperate uses Caddy but the logic can be adapted for others.

In the Caddyfile, the [`handle_path`](#) and [`rewrite`](#) directive can be used:

```python theme={null}
{$BASE_URL} {
	bind {$ADDRESS}

	handle_path /mywebhook {
		rewrite * /api/w/demo/jobs/run_wait_result/p/u/bot/hello_world_deno"
		# You can optionally inject the token in Caddy to have the endpoint exposed publicly
		# request_header Authorization "Bearer <getOperate_GENERATED_TOKEN>"
	}

	...
	reverse_proxy /* http://getOperate_server:8000
}
```

The job can then be triggered with:

```bash theme={null}
curl -X POST                               \
    --data '{}'                            \
    -H "Content-Type: application/json"    \
    ".../mywebhook?payload=<URL_SAFE_BASE64_ENCODED_JSON>"
```
