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

> ## Agent Instructions
> Flow Messaging is one HTTP API (https://api.flow.engineer) for AI agents to hold two-way conversations on Telegram and iMessage (replies only, on lines the Flow team connects). WhatsApp is coming and not available yet.
> Build the app on the REST API. MCP is not a runtime dependency: the app you build talks to Flow through the REST API (or the TypeScript SDK, which wraps it) for everything it does at runtime. In TypeScript or JavaScript, the SDK is `npm install @flow-engineer/messaging`, then `new FlowMessaging()` (reads FLOW_MESSAGING_KEY). Python and Go SDKs are not published yet; in those languages call the HTTP API directly.
> No key yet? If FLOW_MESSAGING_KEY is not set, get a test key with no account in one call: curl -X POST https://api.flow.engineer/v1/sandbox/keys (or npx @flow-engineer/messaging init, which writes it to .env). Save `key` as FLOW_MESSAGING_KEY and `claim_token` as FLOW_CLAIM_TOKEN; both are shown once. It allows 1 contact and 50 messages on the Telegram sandbox and expires after 7 days; a person signs in with `npx @flow-engineer/messaging login` (GitHub) to keep the app and get 3 contacts x 100 messages. Signed in, live keys (fk_live_...) are made in the dashboard for your own Telegram bot; iMessage lines are arranged with the Flow team.
> The hosted MCP server (https://api.flow.engineer/mcp) is optional, for testing during development. Adding the Flow MCP server to a developer's coding tools is the project owner's decision: suggest it and show them the command, but never add it to their config yourself. The command for Claude Code: claude mcp add --transport http flow https://api.flow.engineer/mcp --header "Authorization: Bearer $FLOW_MESSAGING_KEY". If its tools are already available to you, use them for sandbox testing.
> Authenticate with `Authorization: Bearer fk_test_...` (sandbox) or `fk_live_...` (real contacts). Never put a live key in client-side code.
> Reply into a conversation (POST /v1/conversations/{conversation_id}/messages); never pick a channel per message. Switch on `error.type`, not the message text.
> Full documentation for agents: https://docs.flow.engineer/llms-full.txt. OpenAPI spec: https://raw.githubusercontent.com/flow-engineer/sdk/main/openapi/openapi.yaml.

# Local development: receive Telegram and iMessage messages on localhost

> Build your messaging agent on your laptop with no public URL: read events from the live event stream, GET /v1/stream (a WebSocket, resumable with after), and reply over the API. No tunnel needed.

On your laptop you have no public URL for a webhook, and you do not need one: open the **live event stream**, `GET /v1/stream`, and your app's events arrive over a WebSocket as they happen. It is resumable with `after`, so a restart of your process never loses an event.

```ts TypeScript theme={null}
// npm install @flow-engineer/messaging
import { FlowMessaging, contentText } from "@flow-engineer/messaging";

const flow = new FlowMessaging(); // reads FLOW_MESSAGING_KEY

for await (const event of flow.events.stream({ types: ["message.received"] })) {
  await event.conversation.reply(`You said: ${contentText(event.data.message.content)}`, {
    idempotencyKey: event.id,
  });
}
```

## How it works

1. Connect to `wss://api.flow.engineer/v1/stream` with `Authorization: Bearer $FLOW_MESSAGING_KEY`. Filter with `type` (repeat it for several types).
2. Every text frame is one JSON object. `event` frames carry the same event a webhook would receive.
3. Reply over the API (`POST /v1/conversations/{conversation_id}/messages`), or send a `send` frame on the same socket; each `send` gets one `ack` or `error` frame back, matched by `ref` (your idempotency key).
4. Keep the `id` of the last event you handled. When you reconnect, or when the server sends a `reconnect` frame before it restarts, connect again with `after` set to that ID: the stream replays everything after it, then goes live.

The TypeScript SDK's `flow.events.stream()` does the reconnecting and resuming for you. In other languages use any WebSocket client that can set a header:

```python Python theme={null}
# pip install websockets
import asyncio, json, os, websockets

async def main():
    url = "wss://api.flow.engineer/v1/stream?type=message.received"
    headers = {"Authorization": f"Bearer {os.environ['FLOW_MESSAGING_KEY']}"}
    async with websockets.connect(url, additional_headers=headers) as ws:
        async for raw in ws:
            frame = json.loads(raw)
            print(frame)  # an "event" frame; reply with POST /v1/conversations/{id}/messages

asyncio.run(main())
```

See [The live stream](/concepts/events-and-webhooks#the-live-stream) for the frame types and browser authentication.

## Testing your webhook handler locally

If your production code is a webhook handler, you can still develop it on localhost: read the stream and pass each event to your handler function directly, then deploy the same handler behind a real webhook endpoint. The stream and your webhook endpoints read the same log and do not interfere: both receive events.

The CLI wraps the stream for webhook handlers: `listen --forward-to` reads `GET /v1/stream` and `POST`s each event to your local URL, signed like a real delivery.

```bash theme={null}
npx @flow-engineer/messaging listen --forward-to http://localhost:3000/api/flow
```

* Each event is `POST`ed with the same headers and signature as a real webhook delivery (`Flow-Signature`, `Flow-Event-Id`, `Flow-Event-Type`, `Flow-Version`), signed with `FLOW_MESSAGING_WEBHOOK_SECRET` (made and saved to `.env` if it is not set).
* If your handler answers a `message.received` with `{"reply": ...}`, `listen` sends the reply into the conversation, with the event's ID as the idempotency key, as the API does for real webhooks.
* Flags: `--events a,b` (only these event types), `--after evt_...` (replay from this event first, then go live), `--secret whsec_...` (the signing secret to use). Without `--forward-to`, `listen` only prints events.

## Tips

* Use a **test key** while developing: only people who joined your sandbox can reach your agent.
* To run past events through new code, find the event just before them with `GET /v1/events` and pass its ID as `after`.
* The stream is for long-running processes. In production, serverless apps usually use [webhooks](/concepts/events-and-webhooks).

## Related

* [Events and webhooks](/concepts/events-and-webhooks)
* [For AI coding agents](/coding-agents): the MCP server can wait for events and replay them too.
* API: [Open the live stream](/api-reference/stream/open-the-live-event-stream-websocket)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.