---
title: "Use LangGraph with FlowRunner"
description: "Send a LangGraph interrupt() to a person in Slack or email, wait days for the answer, resume with Command(resume=...), and pipe LangSmith Deployment webhooks into flows."
url: https://flowrunner.ai/alternatives/langgraph/with-flowrunner
date_published: 2026-11-02
date_modified: 2026-11-03T09:28:56-08:00
---

[Alternatives](https://flowrunner.ai/alternatives) / [LangGraph](https://flowrunner.ai/alternatives/langgraph) / Use with FlowRunner

![LangGraph logo](https://flowrunner.ai/assets/alternatives/langgraph.svg)

# Use LangGraph with FlowRunner

Send a LangGraph interrupt() to a person in Slack or email, wait days for the answer, resume with Command(resume=...), and pipe LangSmith Deployment webhooks into flows.

2026-11-02 Updated November 3, 2026 9 min

LangGraph already knows how to pause. A node calls `interrupt()`, the checkpointer saves the thread, and the graph waits until you resume it. This guide is about everything around that pause: getting the payload to the person who decides, waiting for them with reminders, bringing the answer back as `Command(resume=...)`, and keeping a record of it. Then two smaller connections: finished runs on LangSmith Deployment flowing into business steps, and business events starting graph runs.

Your graph stays where it runs, in your process or on LangSmith Deployment. FlowRunner doesn’t run Python. You build the flows from a chat with FlowRunner Prompt to Flow, and the code below is plain `requests` and FastAPI around a compiled graph.

## 1\. Connect FlowRunner Prompt to Flow

[FlowRunner Prompt to Flow](https://docs.flowrunner.ai/build/prompt-to-flow.html) is an MCP server you add to the AI tool you already use. You connect it once and sign in with your FlowRunner account. The server URL is the same for every tool: `https://app.flowrunner.ai/mcp`.

Claude (web and desktop)

1.  In Claude, open **Settings** > **Connectors**.
2.  Click **\+ Add**, then **Add custom connector**.
3.  Enter `FlowRunner` as the name and `https://app.flowrunner.ai/mcp` as the server URL. If Claude offers sign-in choices, pick **Sign in now** and **Register automatically**, and leave the client ID and secret empty.
4.  Click **Add**, then **Connect**, and approve the FlowRunner sign-in page that opens.
5.  In a chat, click **+** > **Connectors** and check that FlowRunner is on.

On Claude's Team or Enterprise plan, an Owner adds FlowRunner once for everyone under **Organization settings** > **Connectors**, with the same URL. Each member then finds it under **Settings** > **Connectors** and clicks **Connect**.

Claude Code

1.  In your project folder, add the server. Add `--scope user` to use it in every project.
    
    ```
    claude mcp add --transport http flowrunner https://app.flowrunner.ai/mcp
    ```
    
2.  Sign in, and approve the FlowRunner sign-in page that opens in your browser.
    
    ```
    claude mcp login flowrunner
    ```
    
3.  Check the connection with `claude mcp list`. The `flowrunner` line ends in `✔ Connected`.
    

ChatGPT

1.  On the web, open [ChatGPT Plugins](https://chatgpt.com/plugins). ChatGPT connects to MCP servers on chatgpt.com only.
2.  Click **Add**, then **Add custom MCP server**.
3.  Enter `FlowRunner` as the name and, under **Connection**, `https://app.flowrunner.ai/mcp` as the server URL.
4.  Choose **OAuth**, select **I understand and want to continue**, then **Create as a plugin**.
5.  Install the plugin and approve the FlowRunner sign-in page.
6.  In a chat, type `@` and pick FlowRunner.

Whether you can add a custom MCP server depends on your ChatGPT plan and, in a Business, Enterprise or Edu workspace, on your admin's settings.

Codex

1.  Add these lines to `~/.codex/config.toml`, creating the file if it doesn't exist.
    
    ```
    [mcp_servers.flowrunner]
    url = "https://app.flowrunner.ai/mcp"
    ```
    
2.  Sign in, and approve the FlowRunner sign-in page that opens in your browser.
    
    ```
    codex mcp login flowrunner
    ```
    
3.  Check the connection with `codex mcp list`. The `flowrunner` row shows `OAuth` in the **Auth** column.
    

Cursor

1.  Create `~/.cursor/mcp.json` to use FlowRunner in every project, or `.cursor/mcp.json` in a project folder for that project only. If the file already exists, add the `flowrunner` entry inside its `mcpServers`.
    
    ```
    {
      "mcpServers": {
        "flowrunner": { "url": "https://app.flowrunner.ai/mcp" }
      }
    }
    ```
    
2.  Open **Customize** in the sidebar, then **MCPs**, and follow the sign-in prompt for `flowrunner`.
    

VS Code (GitHub Copilot)

1.  Open the Command Palette, run **MCP: Add Server**, choose **HTTP**, enter `https://app.flowrunner.ai/mcp`, and name it `flowrunner`.
2.  Open Copilot Chat in agent mode and ask "List my FlowRunner workspaces". The first time, VS Code asks you to sign in: allow it and approve the FlowRunner sign-in page.
3.  Copilot lists your workspaces. FlowRunner is connected.

On the FlowRunner sign-in page, check the account under **Logged in as**, tick **I recognize and trust this URL**, and click **Authorize**. If you don't have a FlowRunner account yet, the [Free plan](https://flowrunner.ai/pricing) needs no card, and every new account starts with 14 days of Professional. The [setup guide in the docs](https://docs.flowrunner.ai/build/prompt-to-flow.html) has every tool step by step.

The code needs the workspace id and API key from **Workspace Settings** > **General** > **Credentials**, in `FLOWRUNNER_WORKSPACE_ID` and `FLOWRUNNER_KEY` on the server. The key goes in the `FR-Access-Key` header.

## Send an interrupt to a person, resume on the answer

The example: a graph handles refund requests. For anything over a set amount, a node pauses for the support lead. The support lead lives in Slack and has never heard of LangGraph.

**The node.** It pauses with everything the reviewer needs, and nothing else:

```
from langgraph.types import interrupt

def review_refund(state: RefundState):
    decision = interrupt({
        "customer": state["customer"],
        "orderId": state["order_id"],
        "amount": state["amount"],
        "reason": state["reason"],
        "agentSummary": state["summary"],
    })
    return {"approved": decision["approved"], "review_note": decision.get("note", "")}
```

Compile the graph with a durable checkpointer, `graph = builder.compile(checkpointer=checkpointer)`. LangGraph’s docs call `InMemorySaver` a development tool, and this thread may wait through a few deploys.

**Build the approval flow:**

> Build a flow called “Refund review” that starts from the Call Flow API. Its Initial Data is threadId, customer, orderId, amount, reason, agentSummary, and callbackUrl. Send the support lead a Slack direct message with those details and Approve and Reject buttons, then wait for the click. If there’s no answer in 4 hours, send a reminder. If there’s still none after 24 hours, post the request in #support-leads for anyone to answer. If nobody has answered after 3 days, treat it as rejected with the note “No answer in 3 days”. When there’s a decision, POST threadId, approved (true or false), the note, and this run’s Execution ID to callbackUrl, with an X-Callback-Secret header whose value comes from an API KEY placeholder called callbackSecret, and retry on 5XX and network errors. Before you build, tell me anything you can’t map one to one, and ask me about anything I haven’t said.

Save the secret first, on the Custom tab of [API Keys](https://docs.flowrunner.ai/platform/api-keys.html) under **Connections**. The flow reads it through a [placeholder](https://docs.flowrunner.ai/learn/concepts/placeholders.html) of type API KEY: you pick the saved key in the flow’s **Settings** > **Placeholder Data**, so the secret stays out of the chat and out of the blocks.

If the reviewer should change the amount rather than only approve or reject, a FlowRunner [Form](https://docs.flowrunner.ai/platform/forms.html) can show the request as editable fields behind a public link. FlowRunner Prompt to Flow can’t create Forms yet, so it tells you what to add in the flow editor.

**Hand off the interrupt from the app.** Send the request after `invoke` returns, not inside the node. On resume, LangGraph runs the node again from its start, so anything the node does before `interrupt()` happens twice:

```
import os

import requests

REVIEW_URL = (
    "https://api.flowrunner.ai/api/v1/"
    f"{os.environ['FLOWRUNNER_WORKSPACE_ID']}/flows/<flow-id>/activate"
)

def start_refund(request_id: str, refund: dict):
    config = {"configurable": {"thread_id": request_id}}
    result = graph.invoke(refund, config)
    if "__interrupt__" in result:
        payload = result["__interrupt__"][0].value
        resp = requests.post(
            REVIEW_URL,
            headers={"FR-Access-Key": os.environ["FLOWRUNNER_KEY"]},
            json={"threadId": request_id, **payload, "callbackUrl": os.environ["RESUME_URL"]},
            timeout=30,
        )
        resp.raise_for_status()
        save_pending(request_id, resp.json()["executionId"])
    return result
```

The [non-blocking Call Flow API](https://docs.flowrunner.ai/api/call-flow-nonblocking.html) returns the run’s `executionId` straight away, and the FlowRunner run waits for the support lead from there.

**Resume on the answer.** The flow POSTs the decision to `RESUME_URL`:

```
import hmac
import os

from fastapi import FastAPI, Header, HTTPException
from langgraph.types import Command

app = FastAPI()

@app.post("/refunds/resume")
def resume(body: dict, x_callback_secret: str = Header(default="")):
    if not hmac.compare_digest(x_callback_secret, os.environ["CALLBACK_SECRET"]):
        raise HTTPException(status_code=401)
    if not mark_resumed(body["threadId"], body["executionId"]):
        return {"ok": True}  # already resumed: a retried delivery
    config = {"configurable": {"thread_id": body["threadId"]}}
    graph.invoke(
        Command(resume={"approved": body["approved"], "note": body.get("note", "")}),
        config,
    )
    return {"ok": True}
```

The value you pass in `Command(resume=...)` becomes the return value of `interrupt()`, so `review_refund` gets `approved` and `note` and the graph carries on. `mark_resumed` is your own check that this thread hasn’t already been resumed, since the flow’s Retry Policy can deliver twice if your endpoint fails partway.

What you get from the split: the thread waits in your checkpointer, the person is chased by the flow, and the FlowRunner run shows what was asked, the reminder, who it went to, and the answer, to anyone on the team. The flow can wait up to 30 days on Free, Starter, and Growth, and up to a year on Professional and above, and the whole review is one execution. This example handles one interrupt at a time; if your graph can raise several at once, send each one and match the answers to them before resuming.

## Send finished runs into a flow

On LangSmith Deployment, you can pass a `webhook` URL when you create a run, and LangSmith sends a request when the run completes. The payload carries the `run_id`, the `thread_id`, the `status`, and, for stateful runs, the thread’s `values`. Point it at a flow and a finished run turns straight into business steps.

**Build the flow:**

> Build a flow that starts with an External Callback and receives a LangSmith Deployment run webhook (run\_id, thread\_id, status, and values). If status is success and values.approved is true, create the refund note on the Zendesk ticket in values.ticketId, email the customer at values.customerEmail with the outcome, and post a summary to #support. If status is error or timeout, post the run\_id and the error to #support-eng. Otherwise do nothing. Ask me about anything I haven’t said.

A run that stopped at an interrupt is reported with status `success`, which is why the flow checks the values and not only the status.

**Keep the key out of the URL.** Use the header form of the External Callback address, `https://api.flowrunner.ai/api/v1/{workspace-id}/flows/{flow-id}/triggers/{block-id}/activate` (the ids are in the block’s Callback URL, [explained here](https://docs.flowrunner.ai/api/activating-a-trigger.html)), and set the key as a static webhook header in `langgraph.json`:

```
{
  "webhooks": {
    "headers": {
      "FR-Access-Key": "${{ env.LG_WEBHOOK_FR_KEY }}"
    }
  }
}
```

By default LangSmith only reads environment variables with the `LG_WEBHOOK_` prefix here, so name the variable that way.

## Start a graph run from a business event

When a graph should run because something happened in a business app, a flow can catch it with that app’s trigger, with the account connected once, and call your graph with only the fields it needs.

Put a small endpoint in front of the graph that accepts the job and returns at once:

```
from fastapi import BackgroundTasks

@app.post("/refunds/start")
def start(body: dict, background: BackgroundTasks):
    # check the Authorization header against your token here
    background.add_task(start_refund, body["requestId"], body["refund"])
    return {"accepted": True}
```

Then build the trigger side:

> Build a flow that starts on a Zendesk ticket event when a ticket is tagged “refund-request”. Get the ticket and its comments. POST requestId (the ticket id) and refund (customer, orderId, amount, reason, and the latest comment) to [https://agents.example.com/refunds/start](https://agents.example.com/refunds/start), with an Authorization header of Bearer followed by an API KEY placeholder called refundsToken, retrying on 429, 5XX, and network errors. Add an internal note to the ticket saying the request is being reviewed. Ask me about anything I haven’t said.

The approval from the first section then happens inside that same graph run. If your graph runs on LangSmith Deployment, the flow can call the deployment over HTTP in the same way.

## Let a FlowRunner agent call your graph

A FlowRunner AI Agent block can use tools from any MCP server you [register in FlowRunner](https://docs.flowrunner.ai/platform/mcp-servers.html), as long as its URL is publicly reachable and it accepts a token header or OAuth 2.0. Agent Server exposes deployed agents as MCP tools at `/mcp`, and the [LangChain guide](https://flowrunner.ai/alternatives/langchain/with-flowrunner) shows a small MCP server written with the MCP Python SDK. Either one, registered in the console, lets a FlowRunner agent hand a task to your graph when it decides to. FlowRunner Prompt to Flow can’t register MCP servers yet, so that step is in the console.

## Optional: turn a graph into a flow

Some graphs are a few model calls, some branches, and an approval. If you want one of those on the FlowRunner canvas, FlowRunner Prompt to Flow can read the graph code and build the equivalent flow:

> Read this LangGraph graph and build the equivalent FlowRunner flow. Each node that calls a model becomes an AI Agent block with the same instructions, each conditional edge becomes a condition or router, and each interrupt() becomes a human approval step. Tell me which nodes run custom code that should stay in my service and be called over HTTP, and ask me about anything the code doesn’t say.

That’s an option, not a recommendation. A graph with custom state logic, your own models, or tuned retrieval belongs in LangGraph, and the patterns above connect it to FlowRunner where it runs.

Questions about wiring LangGraph to FlowRunner? [Contact us](https://flowrunner.ai/contact).

## Frequently asked questions

### Why send the interrupt to FlowRunner from the app and not from inside the node?

Because LangGraph re-runs the node from its start when you resume. Code before interrupt() runs again, so a request sent from inside the node would go out a second time when the answer comes back. Sending it from the app, after invoke returns with an interrupt, sends it once.

### Which checkpointer should I use when the answer can take days?

A durable one. LangGraph's docs call InMemorySaver a development tool and advise a durable checkpointer in production. A database-backed checkpointer keeps the paused thread through restarts and deploys, so the resume call works whenever FlowRunner delivers the decision. On LangSmith Deployment's Agent Server, checkpoints are handled for you.

### What happens if nobody answers?

You decide in the flow. It can remind the reviewer, move the request to a backup approver, and after a deadline you set, send a decision of its own, such as a rejection with a note, so the graph doesn't sit waiting for an answer that isn't coming. A FlowRunner run can wait up to 30 days on Free, Starter, and Growth, and up to a year on Professional and above.

### Can I use this with LangSmith Deployment instead of my own server?

Yes. The approval pattern works the same way, with your resume step calling the deployment to continue the thread. LangSmith Deployment's completion webhook can also point at a FlowRunner flow that starts with an External Callback, with the FlowRunner key set as a static header in langgraph.json.

### Does the waiting approval flow count as many executions?

No. One complete flow run is one execution, however many steps it has and however long it waits. An approval that takes three days, with a reminder and an escalation, is one execution.

## Keep reading

-   [LangGraph and FlowRunner](https://flowrunner.ai/alternatives/langgraph)

[All alternatives](https://flowrunner.ai/alternatives)

By [Mark Piller](https://flowrunner.ai/about#mark-piller), Founder of FlowRunner [Editorial policy](https://flowrunner.ai/editorial-policy)

---
Markdown version of https://flowrunner.ai/alternatives/langgraph/with-flowrunner. Site index: https://flowrunner.ai/llms.txt
