Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@
{
"group": "Workflows",
"pages": [
"product/process/workflows/overview"
"product/process/workflows/overview",
"product/process/workflows/triggers"
]
},
{
Expand Down Expand Up @@ -332,7 +333,8 @@
"pages": [
"reference/api/endpoints/activity-logs",
"reference/api/endpoints/admin-logs",
"reference/api/endpoints/notes"
"reference/api/endpoints/notes",
"reference/api/endpoints/trigger-workflow-webhook"
]
}
]
Expand Down
Binary file added images/workflows/webhook-regenerate-url.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/workflows/webhook-trigger-rows.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions product/execute/workflows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Workflows can be executed from:

The workflow opens in a guided panel. The user works through each step; the workflow context accumulates as they go.

Workflows can also be started automatically by an external system through a [webhook trigger](/product/process/workflows/triggers), without a user in the interface.

## AI-powered steps

The **Get Data**, **Update Data**, **Trigger Action**, and **MCP Task** steps surface the relevant data or actions automatically based on the workflow's context. Each completed step adds to that context, helping the AI suggest better defaults and pre-fills for subsequent steps, for example, pre-filling fields when updating a record, or composing an email with the right context when notifying a customer through a tool like Zendesk.
Expand Down
8 changes: 8 additions & 0 deletions product/process/workflows/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ By default, workflows are available when selecting a single record from the List

Workflows can also be made available in Summary Views and Workspaces.

### Triggering workflows

By default, workflows are started manually by users from the interface. A workflow can also be triggered automatically by an external system through a **webhook**, configured in the **Process** section of the workflow settings.

<Card title="Workflow triggers" icon="bolt" href="/product/process/workflows/triggers">
Enable manual and webhook triggers, and manage the webhook URL and token
</Card>

---

## Security & auditing
Expand Down
89 changes: 89 additions & 0 deletions product/process/workflows/triggers.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
title: "Workflow triggers"
description: "Choose how a workflow starts, manually from the interface, or automatically from an external system via a webhook."
---

A workflow can be started in two independent ways. Both are configured in the **Process** section of the workflow settings page, beneath the version card. Each trigger type has its own row with an on/off toggle, and the two can be enabled independently.

- **Manual** — users start the workflow from a matching record in the interface (List View, Summary/Details, or a Workspace). This is the default. See [Executing workflows](/product/execute/workflows).
- **Webhook** — external systems start the workflow via an authenticated HTTP POST. **Disabled by default.**

Webhook triggers are available on **all environments**, with no production-only restriction.

## The webhook trigger

When enabled, the webhook lets any external system, an ETL job, a partner service, a CRON in your own infrastructure, start a workflow run on a specific record by calling a stable URL.

Two things are separated by design:

- **The URL** identifies everything *fixed* about the trigger: which workflow runs, and against which rendering. The only per-call input is the target record.
- **The token** carries authentication *and* the identity the run acts as. The run reads and writes data as the token's user, and the activity log attributes it to that user, so a webhook-triggered run can never do more than that user is allowed to.

For the full HTTP contract, request body, response codes, idempotency, and rate limits, see the [Trigger a workflow via webhook](/reference/api/endpoints/trigger-workflow-webhook) API reference.

## Enabling the webhook

1. Open the workflow settings page and go to the **Process** section.
2. Toggle **Webhook** on.

Once enabled:

- the endpoint **URL** is displayed inline with a **copy** button;
- a hint shows the JSON body to send, with the target record's `record_id`;
- a **Generate new URL** button lets you rotate the URL (see [Regenerating the URL](#regenerating-the-url)).

<Frame>
<img src="/images/workflows/webhook-trigger-rows.png" alt="Process section with Manual and Webhook trigger rows beneath the version card" />
</Frame>

Copy the URL and use it from your external system with a valid application token. The workflow starts on the record you pass in the request body.

## Regenerating the URL

If a URL may have leaked, or you simply want to rotate it, generate a new one from the **Generate new URL** button below the current URL.

<Warning>
Generating a new URL **immediately invalidates the current one**. Any integration still calling the old URL will start failing until you update it with the new URL.
</Warning>

When you confirm:

- the URL is updated inline, and the **copy** button now copies the new one;
- a confirmation toaster briefly appears.

<Frame>
<img src="/images/workflows/webhook-regenerate-url.png" alt="Generate new URL button and the invalidation warning" />
</Frame>

## Revoking access

You have three independent levers to stop a webhook, without necessarily touching the others:

| Lever | Effect | Calls then return |
|---|---|---|
| **Disable the Webhook toggle** | Turns the trigger off. URL and token are unchanged. | `404` |
| **Generate new URL** | Invalidates the current URL. | `400` (old URL) |
| **Invalidate / expire the token** | Done by the token's user, from account settings. | `401` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium workflows/triggers.mdx:66

The docs say to invalidate or expire the application token "from account settings," but tokens are actually managed under Project Settings > API Access, not account settings. A user following these instructions will look in the wrong place and be unable to promptly revoke a compromised webhook token. Correct the location reference so users can find the token management UI quickly.

Suggested change
| **Invalidate / expire the token** | Done by the token's user, from account settings. | `401` |
| **Invalidate / expire the token** | Done by the token's user, from `Project Settings > API Access`. | `401` |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @product/process/workflows/triggers.mdx around line 66:

The docs say to invalidate or expire the application token "from account settings," but tokens are actually managed under `Project Settings > API Access`, not account settings. A user following these instructions will look in the wrong place and be unable to promptly revoke a compromised webhook token. Correct the location reference so users can find the token management UI quickly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dismissing this one: the workflow webhook trigger authenticates with an API token managed in Account Settings, which is a different scope from the public-API tokens under Project Settings > API Access. So invalidating the token from account settings is correct here. The canonical authentication.mdx also documents token creation under Account Settings.

The other three comments were addressed in 508884f (moved the endpoint out of the public-API table into a Note, added a response.ok check to the Node.js example, and scoped the "safe to retry" guidance to in-flight runs).


Turning the toggle back on re-enables the *same* URL and token — it is a pause, not a reset.

## Auditing

Every webhook-triggered run is recorded in the workflow run history and in your **Activity Logs**, attributed to the token's user and marked as **webhook-triggered** so you can distinguish automated runs from manual ones.

## Learn more

<CardGroup cols={2}>
<Card title="Trigger via webhook (API)" icon="code" href="/reference/api/endpoints/trigger-workflow-webhook">
The HTTP contract: body, response codes, idempotency, rate limits.
</Card>
<Card title="Workflows overview" icon="diagram-project" href="/product/process/workflows/overview">
Build and manage workflows in the no-code editor.
</Card>
<Card title="Executing workflows" icon="play" href="/product/execute/workflows">
How operators run workflows from the interface.
</Card>
<Card title="Roles & permissions" icon="shield" href="/get-started/control/roles-permissions">
Control what a workflow run can access.
</Card>
</CardGroup>
160 changes: 160 additions & 0 deletions reference/api/endpoints/trigger-workflow-webhook.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
title: Trigger a workflow via webhook
description: "Start a workflow run on a specific record from an external system, over authenticated HTTP."
---

## Trigger a workflow run

Starts a run of a workflow on a specific record. External systems call the workflow's webhook URL to trigger it the same way an operator would from the interface, but over authenticated HTTP.

```
POST <webhook_url>
```

You do **not** build this URL yourself. Enable the webhook trigger on the workflow, then **copy the full URL** from its trigger settings — it already contains the signed identifier of the workflow and rendering. See [Workflow triggers](/product/process/workflows/triggers) for how editors enable the webhook, copy the URL, and manage the token.

The run uses the **latest active workflow** for the target rendering. There is no published/draft distinction — "latest active" is the contract.

### Authentication

All requests require a Forest **application token** in the `Authorization` header, presented with the Bearer scheme. Generate one from [your account settings](https://app.forestadmin.com/user-settings).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium endpoints/trigger-workflow-webhook.mdx:20

The documentation contradicts itself on how authentication works. The "Authentication" section says any Forest application token whose user can reach the rendering will work (lines 26–32), but the "Revoking access" section says expiring "the" application token makes webhook calls return 401 (line 149). Both cannot be true: if any valid user token works, expiring one token would not revoke access because the caller could switch to another valid token. This leads integrators to set up the wrong credentials and provides false revocation guidance. The page should state that callers must use a specific token associated with the webhook, or clarify what "the application token" refers to.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @reference/api/endpoints/trigger-workflow-webhook.mdx around line 20:

The documentation contradicts itself on how authentication works. The "Authentication" section says any Forest application token whose user can reach the rendering will work (lines 26–32), but the "Revoking access" section says expiring "the" application token makes webhook calls return `401` (line 149). Both cannot be true: if any valid user token works, expiring one token would not revoke access because the caller could switch to another valid token. This leads integrators to set up the wrong credentials and provides false revocation guidance. The page should state that callers must use a specific token associated with the webhook, or clarify what "the application token" refers to.


```
Authorization: Bearer YOUR_APPLICATION_TOKEN
```

The token serves two purposes at once:

- **Authentication** — it identifies the caller and grants (or denies) the call.
- **Execution identity** — the run reads and writes data **as the token's user**, and the activity log attributes it to that user. Anything the user is not allowed to access, the run cannot access either.

<Note>
The URL alone grants no authority. It carries a signed identifier of the workflow and rendering, but starts nothing without a token whose user can reach that rendering. Authority comes entirely from the token.
</Note>

<Warning>
If your project uses SSO, the application token must be generated while logged in with SSO.
</Warning>

### The webhook URL

The URL is obtained by copying it from the workflow's trigger settings — you never assemble it from IDs. It is:

- **Stable** — it does not change when the workflow is published again.
- **Signed and tamper-evident** — it embeds the workflow and rendering; a forged or modified URL is rejected.
- **Valid until the editor invalidates it** — regenerating the URL (or disabling the trigger) is how access is revoked. See [Revoking access](#revoking-access).

Treat the URL as a credential and store it alongside the token.

### Request body

The body is JSON and is validated. Only known fields are extracted; unknown fields are ignored.

```json
{
"record_id": "42"
}
```

| Field | Type | Description |
|---|---|---|
| `record_id` | string | The record the workflow runs on. Composite primary keys are supported in their packed form (e.g. `"123|456"`). |

<Note>
The `record_id` is **not** verified when the run is created. If the record does not exist or is inaccessible to the token's user, the run is still created and fails at its first data step during execution — observable via the run state.
</Note>

Execution is **asynchronous**: the endpoint creates and queues the run, then returns immediately. The run is processed by the executor afterwards.

### Example request

Use the URL you copied from the workflow's trigger settings as-is. In the examples below, `FOREST_WEBHOOK_URL` holds that copied URL.

**cURL:**

```bash
curl -X POST "$FOREST_WEBHOOK_URL" \
-H "Authorization: Bearer $FOREST_APPLICATION_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "record_id": "42" }'
```

**JavaScript (Node.js):**

```javascript
const response = await fetch(process.env.FOREST_WEBHOOK_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FOREST_APPLICATION_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ record_id: '42' }),
});

if (!response.ok) {
throw new Error(`Webhook request failed: ${response.status}`);
}

const { runId } = await response.json();
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
```

### Response

On success the endpoint returns `202 Accepted` with the id of the created run:

```json
{
"runId": "b1e6c2a4-7f3d-4e2a-9c1b-8d5f0a2e3c4d"
}
```

Use `runId` to follow the run's progress and outcome.

### Idempotency

Only one run of a given workflow can be active on a given record at a time. If a run is already ongoing on the target record, the endpoint returns `409 Conflict` and does **not** start or resume a run:

```json
{
"error": "A run of this workflow is already ongoing on this record."
}
```

This makes retries safe **while a run is still ongoing**: a caller that retries after a network error either starts the run (`202`) or learns one is already in progress (`409`). Note the `409` only holds for the duration of the active run — once it finishes, a new request on the same record starts a fresh run (`202`). Retry to recover from transient failures on the initial call, not to re-drive a record after its run has completed.

### Rate limiting

The endpoint is rate-limited **per webhook**, so one noisy integration cannot starve other webhooks. Exceeding the limit returns `429 Too Many Requests` with a `Retry-After` header (in seconds).

The starting threshold is roughly **60 requests per minute per webhook**, and is configurable. Duplicate-record bursts are additionally absorbed by the single-run-per-record `409`.

### Errors

| Status | Meaning |
|---|---|
| `400` | Invalid JSON body, missing `record_id`, or a malformed, tampered, or superseded URL (e.g. an old URL after it was regenerated). |
| `401` | Missing, expired, or invalid Bearer token. |
| `403` | Token is valid, but its user is not authorized for the target organization, rendering, or workflow. |
| `404` | Unknown webhook, no active workflow for the rendering, or the webhook trigger is disabled. |
| `409` | A run of this workflow is already ongoing on this record (see [Idempotency](#idempotency)). |
| `429` | Rate limit exceeded. The response includes a `Retry-After` header. |

### Revoking access

The webhook keeps working until an editor intervenes. Two independent levers, both from the workflow's trigger settings:

- **Disable the webhook trigger** — the toggle is the revocation switch. The URL and token are unchanged, but calls return `404` while it is off.
- **Regenerate the URL** — invalidates the current URL immediately; the old URL then returns `400`. Update your integration with the new URL.

Independently, the **application token** can be expired or revoked by its user at any time, which makes calls return `401`.

## Learn more

<CardGroup cols={2}>
<Card title="Workflow triggers" icon="bolt" href="/product/process/workflows/triggers">
Enable and configure the webhook trigger from the workflow editor.
</Card>
<Card title="Authentication" icon="key" href="/reference/api/authentication">
Generate and manage application tokens.
</Card>
</CardGroup>
4 changes: 4 additions & 0 deletions reference/api/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ The Forest public API exposes a subset of platform data and operations for progr
| [Admin logs](/reference/api/endpoints/admin-logs) | Track configuration and administrative operations across the project |
| [Notes](/reference/api/endpoints/notes) | Read and write [collaboration notes](/product/collaborate/notes) on records |

<Note>
**Trigger a workflow via webhook** is also available over HTTP, but it does not follow the base URL and path convention below. Its URL is not assembled from your project and environment — you copy the full, signed URL from the workflow's trigger settings. See [Trigger a workflow via webhook](/reference/api/endpoints/trigger-workflow-webhook) for the contract and [Workflow triggers](/product/process/workflows/triggers) for how to generate the URL.
</Note>

If you need data access beyond what's exposed here, you have two options:

- **Use the Forest Agent directly**, the agent serves your collections via its own REST API. See the [Node.js](/reference/agent-api/nodejs) or [Ruby](/reference/agent-api/ruby) agent reference.
Expand Down