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
flow serve my-workflow.flowThis 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:
flow serve ./workflows/Each file automatically gets its own address based on its filename:
workflows/email-verification.flowbecomes available at/email-verificationworkflows/order-processing.flowbecomes available at/order-processing
Options
| Option | What it does | Example |
|---|---|---|
--port | Changes which port the server listens on | --port 4000 |
--verbose | Shows details about each incoming request | --verbose |
--mock | Uses simulated services instead of real ones | --mock |
--auth-token | Requires a Bearer token for all requests | --auth-token my-secret |
--cors | Allows any website to call your server | --cors |
--cors-origin | Allows a specific website to call your server | --cors-origin "https://my-app.com" |
flow serve my-workflow.flow --port 4000 --verbose --mockTriggering a workflow
Send a web request with JSON data. The simplest way is with curl in your terminal:
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
{
"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:
{
"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:
{
"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:
curl http://localhost:3000/healthReturns: { "status": "ok" }
Workflow info
See what workflows are available:
curl http://localhost:3000Single file mode returns the workflow's details:
{
"name": "GitHub Repository Scout",
"version": 1,
"trigger": "when a username is provided"
}Directory mode returns a list of all available workflows:
{
"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:
flow serve ./workflows/ --auth-token my-secret-tokenOr set the FLOW_AUTH_TOKEN environment variable:
export FLOW_AUTH_TOKEN=my-secret-token
flow serve ./workflows/When a token is configured, every request must include an Authorization header:
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:
{
"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:
docker build -t flow-server .Run it, mounting your .flow files into the container:
docker run -p 3000:3000 -v ./workflows:/workflows flow-serverWith authentication and environment variables for your services:
docker run -p 3000:3000 \
-v ./workflows:/workflows \
-e FLOW_AUTH_TOKEN=my-secret-token \
-e API_KEY=your-api-key \
flow-serverThe default command is serve /workflows --port 3000. You can override it for a single file:
docker run -p 3000:3000 \
-v ./my-workflow.flow:/app/my-workflow.flow \
flow-server serve /app/my-workflow.flow --port 3000 --mockCORS (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:
flow serve my-workflow.flow --corsThis 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:
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:
export FLOW_CORS_ORIGIN=https://my-app.example.com
flow serve my-workflow.flowThis 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:
flow serve my-workflow.flow --mockThis 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:
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 popularityStart the server:
flow serve github-scout.flow --verboseTrigger it:
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{"username": "torvalds"}'Response:
{
"status": "completed",
"outputs": {
"name": "Linus Torvalds",
"popularity": "star"
}
}Next steps
- Scheduling — Run workflows on a recurring schedule
- CLI Commands — All available commands and options
- Services — Connect to external services
- Examples — More workflow examples