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

# Connect a Managed Deep Agent over HTTP

> Start Managed Deep Agents runs from any external service that can send a webhook, and return responses through your own transport.

An HTTP channel turns a managed deep agent into an HTTP endpoint that any external service can call. Use it for a provider that Managed Deep Agents does not support directly, such as an order system, a support tool, or your own application.

You supply two callbacks: one that authenticates the request, and one that converts it into a message, naming the caller and the conversation it belongs to. Managed Deep Agents owns the trusted handoff, the agent run, and the reply. For the provider-managed alternative, see [Slack](/langsmith/python/managed-deep-agents-channels-slack).

<Note>
  Managed Deep Agents is in **public [beta](/langsmith/release-stages)** and available on [LangSmith Cloud](/langsmith/cloud) in the US region only.
</Note>

<Note>
  HTTP channels require `managed-deepagents>=0.8.0`.
</Note>

## Project structure

An HTTP channel declaration lives under `channels/`, like any other channel:

```text theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
my-agent/
  agent.py
  channels/
    orders.py
```

The file name becomes the channel name and the endpoint path. A project can declare more than one HTTP channel, and names must be unique. For the full project layout, see [Project structure](/langsmith/python/managed-deep-agents-project-structure).

## Add an HTTP channel

<Steps>
  <Step title="Declare the channel" id="declare-the-channel">
    ```python channels/orders.py theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    from managed_deepagents import channels

    from lib.orders import parse, verify

    channel = channels.http(
        provider="orders",
        verify=verify,
        parse=parse,
    )
    ```

    `provider` names the external service, and it is separate from the channel name. Managed Deep Agents sends it to Agent Auth with the caller's ID, resolving the [principal](/langsmith/python/managed-deep-agents-identity) whose credentials the run may use. The provider therefore namespaces caller IDs.

    Share one provider across channels that serve the same service. Two Shopify channels, `channels/orders` and `channels/refunds`, both declaring `provider: "shopify"`, resolve the same Shopify user to one principal. A channel declaring `provider: "slack"` resolves to a different principal, even when the caller ID is identical.

    Declaring the channel registers the provider, so any non-empty name works.
  </Step>

  <Step title="Verify the request" id="verify-the-request">
    Managed Deep Agents calls `verify` first. Return `True` to continue to `parse`, or `False` to reject the request with `401`. `verify` may be `async def`.

    `verify` receives an `HttpChannelRequest` with a Starlette `request` and the original `raw_body` bytes. Check a signature against the bytes, not against a re-serialized body:

    ```python lib/orders.py theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    import hashlib
    import hmac
    import os

    from managed_deepagents import HttpChannelRequest


    def verify(context: HttpChannelRequest) -> bool:
        secret = os.environ["ORDERS_WEBHOOK_SECRET"]
        digest = hmac.new(secret.encode(), context.raw_body, hashlib.sha256).hexdigest()
        received = context.request.headers.get("x-orders-signature", "")
        return hmac.compare_digest(f"sha256={digest}", received)
    ```

    <Warning>
      The endpoint has no platform authentication. Channel event routes authenticate inside the adapter, not through Managed Deep Agents ingress. That makes `verify` the only thing standing between the public internet and an agent run. Always check a signature or a shared secret, and never accept a request unconditionally.
    </Warning>

    Use a [deployment secret](/langsmith/python/managed-deep-agents-deploy) for the signing key. A `verify` callback that raises rejects the request with `500`.

    Resolve channel credentials from the environment, not with `connections.get`, which requires a run. For credentials the agent's tools use during a run, see [Manage connections](/langsmith/python/managed-deep-agents-connections).
  </Step>

  <Step title="Parse the request into a message" id="parse-the-request">
    `parse` converts a verified request into a message that starts a run, or ignores the event. It receives the same `HttpChannelRequest` as `verify`, so request headers are available here too, and it may be asynchronous. Return one of two shapes:

    * `{"type": "message", "message": {...}}` starts a run.
    * `{"type": "ignore"}` skips the event.

    Both accept an optional `response`, a Starlette `Response`.

    The message carries four required fields:

    * **`user_id`**: The caller's ID in the external service, as a string, resolved from the verified event. Agent Auth maps it to the principal whose credentials the run may use, so derive it from verified data rather than from an unauthenticated field. Convert a numeric provider ID with `str()`.
    * **`thread_id`**: The conversation to run in, as a UUID. Managed Deep Agents lowercases it and maps it to a durable agent thread, so the same value continues the same conversation.
    * **`target`**: A JSON value that identifies the reply destination in the provider. This is separate from the agent thread UUID. Include it even when the channel only starts runs.
    * **`content`**: The message text, or a list of LangChain content blocks.

    <Warning>
      `thread_id` must be a UUID. Managed Deep Agents rejects any other value with `400`, so map an external conversation ID to a UUID before returning it, for example with `uuid.uuid5`.
    </Warning>

    Add `parse` to the `lib/orders` file that contains `verify`. Combine the imports from both examples at the top of the file, keeping only one import of `HttpChannelRequest`:

    ```python lib/orders.py theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    import json
    import uuid

    from managed_deepagents import HttpChannelParseResult, HttpChannelRequest

    ORDERS_NAMESPACE = uuid.UUID("6f0c9a3e-8f1a-4f5e-9c2b-7d4e1a2b3c4d")


    def parse(context: HttpChannelRequest) -> HttpChannelParseResult:
        event = json.loads(context.raw_body)
        if event.get("type") != "order.comment":
            return {"type": "ignore"}
        return {
            "type": "message",
            "message": {
                "user_id": str(event["actor"]["id"]),
                "thread_id": str(uuid.uuid5(ORDERS_NAMESPACE, str(event["order"]["id"]))),
                "content": event["comment"]["body"],
                "target": str(event["order"]["id"]),
            },
            "raw_event": event,
        }
    ```

    Return a `response` to control what the provider receives. Managed Deep Agents sends it after the run is accepted for a message, and immediately for an ignored event. Use it to answer a provider's verification challenge without starting a run:

    This example uses Starlette's `JSONResponse`. Add Starlette to your project dependencies before using it:

    ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    uv add "starlette>=1.6.0"
    ```

    ```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    from starlette.responses import JSONResponse

    return {
        "type": "ignore",
        "response": JSONResponse({"challenge": event["challenge"]}),
    }
    ```
  </Step>

  <Step title="Send replies with post" id="send-replies">
    Add an async `post` callback to deliver the agent's final response to the external service. Omit it for a channel that only starts runs.

    `post` receives one input with the verified `target` and a message. Automatic replies use `type: "content"` with a `content` field. Explicit native messages use `type: "native"` with a `native` field. Return the posted message `id` and an optional `url`.

    These examples support content messages and reject native messages. The `target` is the order ID returned by `parse`.

    ```python channels/orders.py theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    import os

    import httpx
    from managed_deepagents import HttpChannelPostInput, HttpPostedMessage, channels

    from lib.orders import parse, verify


    async def post(input: HttpChannelPostInput) -> HttpPostedMessage:
        if input["type"] != "content":
            raise ValueError("This adapter supports content messages only")
        async with httpx.AsyncClient() as client:
            response = await client.post(
                "https://orders.example.com/api/comments",
                headers={"authorization": f"Bearer {os.environ['ORDERS_API_TOKEN']}"},
                json={"order_id": input["target"], "body": input["content"]},
            )
            response.raise_for_status()
        return {"id": str(response.json()["id"])}


    channel = channels.http(
        provider="orders",
        verify=verify,
        parse=parse,
        post=post,
    )
    ```

    Managed Deep Agents posts the reply after the run finishes, separately from the response the provider already received. Two cases produce no reply: a run that pauses on an [interrupt](/langsmith/python/managed-deep-agents-tools#respond-to-an-interrupt), and a run whose agent already delivered a final message itself. A failed `post` is logged as a delivery failure.

    Add an optional async `on_error(error, target)` callback to handle run or delivery failures. It replaces the default error reply. It receives the verified reply target.
  </Step>
</Steps>

## Call the endpoint

A deployed HTTP channel accepts requests at the channel name, not the provider name:

```text theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
POST https://<deployment-url>/channels/<name>/events
```

A declaration in `channels/orders.py` is reachable at `/channels/orders/events`.

Your `parse` callback decodes the request body. It can read JSON, form data, text, or bytes. The examples above expect JSON.

Managed Deep Agents answers with one of these, unless `parse` returned its own `response`:

| Status | Body                                             | Cause                                                                      |
| ------ | ------------------------------------------------ | -------------------------------------------------------------------------- |
| `202`  | `{"status": "accepted", "deliveryId": "..."}`    | The run started.                                                           |
| `202`  | `{"status": "ignored"}`                          | `parse` returned an ignore result.                                         |
| `400`  | `{"error": "invalid channel payload"}`           | `parse` raised, for example because its decoder rejected the body.         |
| `400`  | `{"error": "invalid channel parse result"}`      | `parse` returned an unrecognized shape.                                    |
| `400`  | `{"error": "invalid channel message"}`           | The message has an invalid caller, thread UUID, content, or JSON `target`. |
| `401`  | `{"error": "invalid channel signature"}`         | `verify` rejected the request.                                             |
| `500`  | `{"error": "channel verification failed"}`       | `verify` raised.                                                           |
| `500`  | `{"error": "channel runtime is not configured"}` | The managed runtime is missing required configuration.                     |
| `500`  | `{"error": "channel run could not be started"}`  | The agent run failed to start.                                             |

A `202` means the run was accepted, not that it finished. The agent's answer arrives later through `post`, if configured.

## Read the event in the agent

Each run carries channel data in [run context](/langsmith/python/managed-deep-agents-middleware#use-runtime-context). The parser examples explicitly include the provider event. These fields appear inside the channel context:

```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
{
  "channel": {
    "provider": "orders",
    "target": "A-1024",
    "raw_event": { "type": "order.comment", "order": { "id": "A-1024" } }
  }
}
```

Return `raw_event` from `parse` to expose JSON provider data at `runtime.channel.raw_event` and `runtime.context.channel["raw_event"]`. If omitted, the context has no `raw_event` key and `runtime.channel.raw_event` is `None`.

Managed Deep Agents builds `runtime.channel.event` from the parsed message. It does not decode the body again or preserve the provider event automatically.

## Deploy the agent

An HTTP channel needs no provider authorization, so deployment is the standard command. Managed Deep Agents mounts the endpoint from the declaration.

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
uv run mda deploy
```

Put the signing key and any reply credentials in the project `.env` so `mda deploy` forwards them as deployment secrets. Then register `https://<deployment-url>/channels/<name>/events` with the external service as its webhook target.

## See also

* [Channels overview](/langsmith/python/managed-deep-agents-channels): understand how channels connect messaging services to an agent.
* [Slack](/langsmith/python/managed-deep-agents-channels-slack): use the provider-managed Slack channel instead.
* [Identity](/langsmith/python/managed-deep-agents-identity): authenticate callers and scope channel runs to the resolved user.
* [Deploy an agent](/langsmith/python/managed-deep-agents-deploy): configure and deploy a managed deep agent.

***

<div className="source-links">
  <Callout icon="terminal-2">
    [Connect these docs](/use-these-docs) to your agent of choice via MCP for real-time answers.
  </Callout>

  <Callout icon="edit">
    [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/managed-deep-agents-channels-http.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
  </Callout>
</div>
