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

# HTTP routes

> Receive webhooks from your own services and other APIs with typed routes, and act on any install from them.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

Routes let other services call your app: a CI system reporting a deployment, Stripe sending a webhook,
a cron job asking for a refresh. A route is a standalone piece, like a block or an event handler.

```ts src/routes/deployWebhook.ts theme={null}
import { defineRoute } from "@reintersect/sdk/app";
import { Deploy } from "../deploy";

export const DeployWebhook = defineRoute({
  method: "POST",
  path: "/webhooks/ci/:installId",
  scopes: ["blocks:write"],
  blocks: { Deploy },
  handler: async ({ request, params, reintersect }) => {
    const event = await verifyCiSignature(request);
    const client = reintersect.forInstall(params.installId);

    await client.blocks.update(Deploy, event.blockId, {
      state: { status: event.ok ? "success" : "failure", steps: event.steps },
    });

    return new Response(null, { status: 204 });
  },
});
```

Add it with `.addRoute(DeployWebhook)`. Routes are app-wide, not tied to an install, so they have no
install config. Use `reintersect.forInstall(installId)` to act for a workspace your app knows; store
the install id when your app is installed, and put it in the URL or the payload you give the other
service.

Using Effect? See [Routes](/effect/commands-and-events#routes) in the Effect guide.

## Where routes are served

| Hosting | URL |
| - | - |
| Reintersect-hosted | `https://<slug>.reintersect.app/<path>`, for example `https://launchpad.reintersect.app/webhooks/ci/inst_123`. |
| Self-hosted | Under the root of your `endpoint`. With the endpoint `https://example.com/api/reintersect`, the route `/webhooks/ci/:installId` is served at `https://example.com/api/webhooks/ci/:installId`. |

`reintersect.app` never carries Reintersect cookies, so the hosted origin is safe to hand to third
parties. Requests to it carry no Reintersect signature: verify them with the sending service's own
scheme.

The Next.js template mounts the app at `src/app/api/[[...path]]/route.ts`, so deliveries go to
`/api/reintersect`, routes to `/api/<path>` and an MCP server to `/api/mcp`. The Hono template mounts
it under `/api`, and the Effect template mounts the delivery path, every route and `/mcp` on the
router through `ReintersectApp.layer`.

## Options

| Option | Type | Description |
| - | - | - |
| `method` | `"GET" \| "POST" \| "PUT" \| "PATCH" \| "DELETE" \| "OPTIONS"` | `HEAD` requests are served by the `GET` route without a body. |
| `path` | string literal | See [Paths](#paths). |
| `handler` | `(context) => Response \| Promise<Response>` | Must return a `Response`. |
| `scopes` | `Scope[]` | Scopes the handler's client calls need. Routes never add scopes to the app: another piece must already declare each one, or `fetch` is a type error. |
| `blocks` | `{ Name: Block }` | Blocks the handler updates or inserts, so the client is typed for them and the app must include them. |
| `env` | `Record<string, Schema \| secret()>` | Environment variables the handler reads. |

## Handler context

| Field | Description |
| - | - |
| `request` | The incoming `Request`. Read its body once. |
| `url` | The parsed `URL`. |
| `params` | Typed from the path: `{ installId: string }` for `/webhooks/ci/:installId`. |
| `env` | The declared environment. |
| `reintersect.forInstall(installId)` | A client acting as your app in that install, with your app's scopes. |
| `waitUntil` | Keeps work running after the response. |

`forInstall` works on both hostings. Self-hosted, the SDK mints a one-hour install token with
`REINTERSECT_APP_CLIENT_ID` and `REINTERSECT_APP_CLIENT_SECRET`. Hosted, it asks Reintersect's
gateway, which checks that the install belongs to your app, is active and runs the version making the
call (a preview counts only on a development install), then adds a token with the install's scopes.
Your code never holds it.

## Paths

| Segment | Example | Matches |
| - | - | - |
| Literal | `/webhooks/stripe` | Exactly that segment. |
| Parameter | `/:installId` | One non-empty segment, decoded, in `params.installId`. Names use letters, digits and `_`. |
| Rest | `/files/*` | Everything after, as `params.rest`. Final segment only. |

Paths start with `/`, have no trailing slash, query, hash, braces, spaces or regex groups, and may not
use `/reintersect` or `/mcp`, which the app uses for deliveries and MCP. Paths must be string literals
so the types can check them; mistakes are type errors such as
`"*" is only allowed as the final path segment`.

When several routes match, the most specific wins: literals beat parameters, and parameters beat a
rest segment. A path that matches with the wrong method gets `405` with an `Allow` header; no match is
`404`. If the handler throws, the app answers `500` and logs the error.

## Test a route

Routes are plain `fetch` handling, so call `app.fetch` in a test:

```ts theme={null}
const response = await app.fetch(
  new Request("https://launchpad.example.com/webhooks/ci/inst_123", { method: "POST", body }),
);

expect(response.status).toBe(204);
```


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