Skip to content

Webhook Server

A webhook is a way for one system to automatically notify another system when something happens. Flow can turn any workflow into a webhook endpoint — meaning other systems can trigger your workflow by sending it a web request.

For example, you could:

  • Have Stripe trigger your workflow when a payment is received
  • Have GitHub trigger your workflow when code is pushed
  • Have a form on your website trigger your workflow when submitted

Starting the server

Serving a single workflow

bash
flow serve my-workflow.flow

This starts a server on your computer (port 3000 by default). Your workflow is now listening for incoming requests.

Serving multiple workflows

If you have a folder with multiple .flow files, you can serve them all at once:

bash
flow serve ./workflows/

Each file automatically gets its own address based on its filename:

  • workflows/email-verification.flow becomes available at /email-verification
  • workflows/order-processing.flow becomes available at /order-processing

Options

OptionWhat it doesExample
--portChanges which port the server listens on--port 4000
--verboseShows details about each incoming request--verbose
--mockUses simulated services instead of real ones--mock
--auth-tokenRequires a Bearer token for all requests--auth-token my-secret
--corsAllows any website to call your server--cors
--cors-originAllows a specific website to call your server--cors-origin "https://my-app.com"
bash
flow serve my-workflow.flow --port 4000 --verbose --mock

Triggering a workflow

Send a web request with JSON data. The simplest way is with curl in your terminal:

bash
curl -X POST http://localhost:3000 \
  -H "Content-Type: application/json" \
  -d '{"username": "octocat"}'

The JSON data you send becomes the request object in your workflow. So in this example, request.username would equal "octocat".

You can also use tools like Postman or any programming language's HTTP library to send requests.

What comes back

When the workflow completes successfully

json
{
    "status": "completed",
    "outputs": {
        "name": "The Octocat",
        "followers": 21724,
        "popularity": "star"
    }
}

The outputs contain whatever your workflow produces with complete with. HTTP status: 200 (success).

When the workflow rejects

If your workflow uses reject with, the response looks like:

json
{
    "status": "rejected",
    "message": "The email address could not be verified"
}

HTTP status: 400 (client error).

When something goes wrong

If an unexpected error occurs during execution:

json
{
    "status": "error",
    "message": "Cannot subtract text from number"
}

HTTP status: 500 (server error).

Built-in endpoints

Every Flow server comes with a couple of helpful endpoints:

Health check

A simple way to check if the server is running:

bash
curl http://localhost:3000/health

Returns: { "status": "ok" }

Workflow info

See what workflows are available:

bash
curl http://localhost:3000

Single file mode returns the workflow's details:

json
{
    "name": "GitHub Repository Scout",
    "version": 1,
    "trigger": "when a username is provided"
}

Directory mode returns a list of all available workflows:

json
{
    "workflows": [
        {
            "name": "Email Verification",
            "route": "/email-verification",
            "trigger": "when a form is submitted"
        }
    ]
}

Error checking at startup

Flow validates all your workflow files when the server starts. If any file has an error, the server won't start — it will show you exactly what's wrong so you can fix it first. This means once the server is running, you can be confident that all your workflows are properly written.

Authentication

You can protect your server so that only authorized clients can trigger workflows. Pass a secret token when starting the server:

bash
flow serve ./workflows/ --auth-token my-secret-token

Or set the FLOW_AUTH_TOKEN environment variable:

bash
export FLOW_AUTH_TOKEN=my-secret-token
flow serve ./workflows/

When a token is configured, every request must include an Authorization header:

bash
curl -X POST http://localhost:3000/email-verification \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer my-secret-token" \
  -d '{"signup": {"email": "test@example.com"}}'

Requests without a valid token receive a 401 Unauthorized response:

json
{
    "error": "Unauthorized",
    "hint": "Provide a valid \"Authorization: Bearer <token>\" header."
}

Health check is always public

The /health endpoint does not require authentication, so load balancers and monitoring tools can check the server without a token.

Docker deployment

Flow includes a Dockerfile for running workflows in a container. Build the image:

bash
docker build -t flow-server .

Run it, mounting your .flow files into the container:

bash
docker run -p 3000:3000 -v ./workflows:/workflows flow-server

With authentication and environment variables for your services:

bash
docker run -p 3000:3000 \
  -v ./workflows:/workflows \
  -e FLOW_AUTH_TOKEN=my-secret-token \
  -e API_KEY=your-api-key \
  flow-server

The default command is serve /workflows --port 3000. You can override it for a single file:

bash
docker run -p 3000:3000 \
  -v ./my-workflow.flow:/app/my-workflow.flow \
  flow-server serve /app/my-workflow.flow --port 3000 --mock

CORS (Cross-Origin Requests)

If a web application running in a browser needs to call your Flow server, you'll need to enable CORS (Cross-Origin Resource Sharing). Without it, browsers will block the request for security reasons.

Allow all origins

The simplest option — any website can call your server:

bash
flow serve my-workflow.flow --cors

This is fine for development and internal tools. For public-facing servers, use a specific origin instead.

Allow a specific origin

Restrict access to a single website:

bash
flow serve my-workflow.flow --cors-origin "https://my-app.example.com"

Only requests from https://my-app.example.com will be accepted. All others will be blocked by the browser.

Environment variable

You can also set the allowed origin via an environment variable:

bash
export FLOW_CORS_ORIGIN=https://my-app.example.com
flow serve my-workflow.flow

This is useful for Docker deployments and CI environments where you don't want to hard-code the origin in the command.

CORS and authentication work together

If you enable both CORS and authentication (--auth-token), browser preflight requests (OPTIONS) will succeed without a token. Only the actual workflow requests (POST) need the token.

Testing mode

Use --mock during development to test your webhook setup without calling real services:

bash
flow serve my-workflow.flow --mock

This is great for:

  • Testing that your webhook integration works correctly
  • Developing without needing API keys
  • Making sure the request and response formats are right

Complete example

Here's a GitHub Scout workflow served as a webhook:

txt
config:
    name: "GitHub Repository Scout"
    version: 1

services:
    GitHub is an API at "https://api.github.com"

workflow:
    trigger: when a username is provided

    step FetchProfile:
        get profile using GitHub at "/users/{request.username}"
            save the result as profile
        set name to profile.name
        set followers to profile.followers

    step Evaluate:
        if followers is above 1000:
            set popularity to "star"
        otherwise:
            set popularity to "newcomer"

    complete with name name and popularity popularity

Start the server:

bash
flow serve github-scout.flow --verbose

Trigger it:

bash
curl -X POST http://localhost:3000 \
  -H "Content-Type: application/json" \
  -d '{"username": "torvalds"}'

Response:

json
{
    "status": "completed",
    "outputs": {
        "name": "Linus Torvalds",
        "popularity": "star"
    }
}

Next steps

Released under the MIT License.