> ## Documentation Index
> Fetch the complete documentation index at: https://langchain-5e9cc07a-preview-docsmd-1789592284-4ee7e30.mintlify.site/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 JSON webhook, and return responses through your own transport.

An HTTP channel turns a managed deep agent into a JSON 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. Managed Deep Agents owns caller handoff, thread selection, the agent run, and the reply. For the provider-managed alternative, see [Slack](/langsmith/javascript/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={null}
my-agent/
  agent.ts
  channels/
    orders.ts
```

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/javascript/managed-deep-agents-project-structure).

## Add an HTTP channel

<Steps>
  <Step title="Declare the channel" id="declare-the-channel">
    ```ts channels/orders.ts theme={null}
    import { channels } from "managed-deepagents";

    import { parse, verify } from "../lib/orders.js";

    export const channel = channels.http({
      provider: "orders",
      verify,
      parse,
    });
    ```

    `provider` is a stable name for the external service, such as `orders`. It identifies the service for [identity](/langsmith/javascript/managed-deep-agents-identity) and reply routing, and it is separate from the channel name. 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` receives an `HttpChannelRequest` with a `request` and the original `rawBody` bytes. Check a signature against the bytes, not against a re-serialized body:

    ```ts lib/orders.ts theme={null}
    import { createHmac, timingSafeEqual } from "node:crypto";
    import type { HttpChannelRequest } from "managed-deepagents";

    export function verify({ request, rawBody }: HttpChannelRequest): boolean {
      const secret = process.env.ORDERS_WEBHOOK_SECRET ?? "";
      const expected = Buffer.from(
        `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`,
      );
      const received = Buffer.from(request.headers.get("x-orders-signature") ?? "");
      return (
        received.length === expected.length && timingSafeEqual(received, expected)
      );
    }
    ```

    <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 return `true` unconditionally.
    </Warning>

    Use a [deployment secret](/langsmith/javascript/managed-deep-agents-deploy) for the signing key. A `verify` callback that raises rejects the request with `500`.
  </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. Return one of two shapes:

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

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

    The message carries three fields:

    * **`userId`**: The caller's ID in the external service, 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.
    * **`threadId`**: 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.
    * **`content`**: The message text, or an array of LangChain content blocks.

    <Warning>
      `threadId` 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.
    </Warning>

    ```ts lib/orders.ts theme={null}
    import type {
      HttpChannelParseResult,
      HttpChannelRequest,
    } from "managed-deepagents";
    import { orderThreadId } from "./ids.js";

    export async function parse({
      request,
    }: HttpChannelRequest): Promise<HttpChannelParseResult> {
      const event = await request.json();
      if (event.type !== "order.comment") {
        return { type: "ignore" };
      }
      return {
        type: "message",
        message: {
          userId: event.actor.id,
          threadId: orderThreadId(event.order.id),
          content: event.comment.body,
        },
      };
    }
    ```

    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:

    ```ts theme={null}
    return {
      type: "ignore",
      response: Response.json({ challenge: event.challenge }),
    };
    ```
  </Step>

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

    `messaging` receives `process.env` and returns an object with a `post` method. `post` receives the reply `content` and the original `event`, and returns the posted message `id` and an optional `url`:

    ```ts channels/orders.ts theme={null}
    import { channels } from "managed-deepagents";

    import { parse, verify } from "../lib/orders.js";

    export const channel = channels.http({
      provider: "orders",
      verify,
      parse,
      messaging: (env) => ({
        async post({ content, event }) {
          const response = await fetch("https://orders.example.com/api/comments", {
            method: "POST",
            headers: { authorization: `Bearer ${env.ORDERS_API_TOKEN}` },
            body: JSON.stringify({ orderId: event.order.id, body: content }),
          });
          const posted = await response.json();
          return { id: posted.id };
        },
      }),
    });
    ```

    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/javascript/managed-deep-agents-tools#respond-to-an-interrupt), and a run whose agent already delivered a final message itself. A `messaging` callback that fails to return a usable transport rejects the request with `500`.
  </Step>
</Steps>

## Call the endpoint

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

```text theme={null}
POST https://<deployment-url>/channels/<name>/events
```

A declaration in `channels/orders.py` or `channels/orders.ts` is reachable at `/channels/orders/events`. The request body must be standard JSON encoded as UTF-8.

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"}`        | The body is not standard JSON, or `parse` raised.                                       |
| `400`  | `{"error": "invalid channel parse result"}`   | `parse` returned an unrecognized shape.                                                 |
| `400`  | `{"error": "invalid channel message"}`        | The message is missing a caller, has a thread that is not a UUID, or has empty content. |
| `401`  | `{"error": "invalid channel signature"}`      | `verify` returned `false`.                                                              |
| `500`  | `{"error": "channel verification failed"}`    | `verify` raised.                                                                        |

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

## Read the event in the agent

Each run carries the provider and the original event as [run context](/langsmith/javascript/managed-deep-agents-middleware#use-runtime-context), so tools and middleware can read the fields the message text does not carry:

```json theme={null}
{
  "provider": "orders",
  "raw_event": { "type": "order.comment", "order": { "id": "A-1024" } }
}
```

`raw_event` is the parsed request body, preserved as received.

## 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.

<CodeGroup>
  ```bash npm theme={null}
  npx mda deploy
  ```

  ```bash pnpm theme={null}
  pnpm exec mda deploy
  ```

  ```bash bun theme={null}
  bunx mda deploy
  ```
</CodeGroup>

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/javascript/managed-deep-agents-channels): understand how channels connect messaging services to an agent.
* [Slack](/langsmith/javascript/managed-deep-agents-channels-slack): use the provider-managed Slack channel instead.
* [Identity](/langsmith/javascript/managed-deep-agents-identity): authenticate callers and scope channel runs to the resolved user.
* [Deploy an agent](/langsmith/javascript/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 Claude, VSCode, and more 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>
