# How Integration Webhooks (Slack, Linear, GitHub) Trigger Session Creation in Background Agents

> Learn how integration webhooks for Slack, Linear, and GitHub trigger background agent session creation. Understand the process from request validation to sandbox spawning.

- Repository: [Cole Murray/background-agents](https://github.com/ColeMurray/background-agents)
- Tags: how-to-guide
- Published: 2026-07-13

---

**Integration webhooks trigger session creation in Background Agents by validating incoming requests from external services, extracting user and repository context, and forwarding standardized `create-session` requests to the Control Plane, which spawns a Modal sandbox and returns a durable session ID.**

The ColeMurray/background-agents repository implements three distinct bot services—**Slack-Bot**, **Linear-Bot**, and **GitHub-Bot**—that transform external events into active coding-agent sessions. Each service follows an identical pipeline: receive HTTP request, verify signatures, parse canonical fields, and invoke the Control Plane's session runtime. This unified architecture ensures that whether a developer comments in Slack, updates a Linear issue, or opens a GitHub pull request, the system provisions a sandboxed environment within seconds.

## The Unified Webhook-to-Session Pipeline

All three integrations execute the same five-step flow to move from external event to active session:

1. **Webhook endpoint receives the HTTP request** – Each bot exposes a dedicated route under its own package: `/events` for Slack, `/webhook` for Linear, and `/github` for GitHub.
2. **Signature and token verification** ensures the request originates from the external service and has not been tampered with.
3. **Payload parsing** extracts canonical fields required by `CreateSessionRequest` as defined in [`packages/shared/src/types/session-api.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/shared/src/types/session-api.ts).
4. **Control Plane client invocation** constructs a request body matching `createSessionRequestSchema` and calls either `createSession` or `createSessionRuntimeClient` from the respective `packages/*/src/sessions/control-plane-client.ts` files.
5. **Session instantiation** – `createSessionRuntimeClient` in [`packages/control-plane/src/session/runtime-client.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/runtime-client.ts) contacts the durable-object **Session DO**, persists a row in the D1 session index table, spawns a **Modal sandbox**, and returns the session ID to the caller.

The resulting pipeline flow is:

```

External Service → Bot Webhook Route → Verify & Parse → Control Plane Client → Session DO + Modal Sandbox → Session ID Returned

```

## Slack Webhook Implementation

The Slack bot listens for interaction events at [`packages/slack-bot/src/routes/events.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/slack-bot/src/routes/events.ts). Upon receiving a payload, it validates the Slack signature using headers `x-slack-signature` and `x-slack-request-timestamp`, then dispatches to the event handler:

```typescript
// packages/slack-bot/src/routes/events.ts
eventRoutes.post("/events", async (c) => {
  const signature = c.req.header("x-slack-signature") ?? null;
  const timestamp = c.req.header("x-slack-request-timestamp") ?? null;
  const rawPayload = await c.req.text();

  // Verify Slack signature …
  const parsedPayload = slackInteractionPayloadSchema.safeParse(rawPayload);
  if (!parsedPayload.success) throw new Error("Invalid payload");

  // Dispatch to the Slack event handler which eventually calls createSession()
  await handleSlackEvent(env, parsedPayload.data);
});

```

The event handler delegates to `launchSession` in [`packages/slack-bot/src/sessions/session-launcher.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/slack-bot/src/sessions/session-launcher.ts), which imports the Control Plane client and constructs the session request:

```typescript
// packages/slack-bot/src/sessions/session-launcher.ts
import { createSession } from "./control-plane-client";

export async function launchSession(userId: string, repoInfo: RepoInfo) {
  const session = await createSession(env, {
    source: "slack",
    actorUserId: userId,
    repositories: [{ repo: repoInfo.repo, branch: repoInfo.branch }],
  });
  return session.sessionId;
}

```

## Linear Webhook Implementation

The Linear bot handles incoming webhooks at [`packages/linear-bot/src/webhook-handler.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/linear-bot/src/webhook-handler.ts). After validating the Linear signature, it maps the issue and branch information to the standardized request shape:

```typescript
// packages/linear-bot/src/webhook-handler.ts
export async function webhookHandler(req: Request) {
  const body = await req.json();
  // Validate Linear signature (omitted for brevity)

  // Linear provides issue/branch info – map to CreateSessionRequest shape
  const payload = {
    source: "linear",
    actorUserId: body.actor.id,
    repositories: [{ repo: body.repository.id, branch: body.branch.name }],
  };

  // Direct call to the same control‑plane helper
  const result = await createSession(env, payload);
  return new Response(JSON.stringify({ sessionId: result.sessionId }));
}

```

This handler directly constructs the payload and invokes `createSession`, bypassing intermediate dispatchers while maintaining the same schema contract.

## GitHub Webhook Implementation

The GitHub integration resides in the Control Plane package at [`packages/control-plane/src/webhooks/github.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/webhooks/github.ts). It uses the runtime client factory to instantiate a session manager:

```typescript
// packages/control-plane/src/webhooks/github.ts
import { createSessionRuntimeClient } from "../session/runtime-client";

export async function handleGithubWebhook(env, ctx, request) {
  const body = await request.json();
  // Verify GitHub signature …

  const sessionRuntime = createSessionRuntimeClient(env, ctx);
  const session = await sessionRuntime.createSession({
    source: "github",
    actorUserId: body.sender.login,
    repositories: [{ repo: body.repository.full_name, branch: body.pull_request.head.ref }],
  });

  return new Response(JSON.stringify({ sessionId: session.id }));
}

```

In this implementation, `createSessionRuntimeClient` returns an object exposing the `createSession` method, which ultimately persists the session state and provisions the Modal sandbox.

## Shared Session Schema

All three bots serialize their extracted context into a uniform payload defined by Zod in the shared types package:

```typescript
// packages/shared/src/types/session-api.ts
export const createSessionRequestSchema = z.object({
  source: z.enum(["slack", "linear", "github"]),
  actorUserId: z.string(),
  repositories: z.array(
    z.object({ repo: z.string(), branch: z.string() })
  ),
  // …optional fields such as environmentId, tags, etc.
});

```

This schema ensures type safety across the Slack, Linear, and GitHub implementations while allowing the Control Plane to handle session creation polymorphically regardless of the originating service.

## Summary

- **Three bot services** (Slack, Linear, GitHub) listen for external events on dedicated routes and verify incoming signatures before processing.
- **Canonical field extraction** maps disparate webhook payloads to the uniform `createSessionRequestSchema` defined in [`packages/shared/src/types/session-api.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/shared/src/types/session-api.ts).
- **Control Plane abstraction** – Calls to `createSession` or `createSessionRuntimeClient` delegate session instantiation to [`packages/control-plane/src/session/runtime-client.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/runtime-client.ts).
- **Session creation** persists state in the D1 Session DO and spawns a Modal sandbox, returning a durable session ID to the webhook caller.
- **Unified architecture** allows ColeMurray/background-agents to treat Slack messages, Linear issues, and GitHub pull requests as equivalent triggers for background agent execution.

## Frequently Asked Questions

### How does Background Agents verify that webhooks actually come from Slack, Linear, or GitHub?

Each bot implements signature verification specific to the external service. The Slack bot checks `x-slack-signature` and `x-slack-request-timestamp` headers in [`packages/slack-bot/src/routes/events.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/slack-bot/src/routes/events.ts). The Linear and GitHub bots perform comparable HMAC signature validation against shared secrets before parsing the JSON payload, ensuring that only genuine service requests trigger session creation.

### What happens if the `createSession` call fails after the webhook is received?

If the Control Plane client throws an error during session creation, the bot's route handler will typically return an HTTP error response (500 or 502) to the external service. Because the webhook has already been acknowledged, the external service (Slack, Linear, or GitHub) may retry delivery depending on its own retry policies, though the specific error handling logic depends on the individual bot's implementation in its respective [`webhook-handler.ts`](https://github.com/ColeMurray/background-agents/blob/main/webhook-handler.ts) or route file.

### Can I add custom metadata to the session when triggering from a webhook?

Yes. The `createSessionRequestSchema` in [`packages/shared/src/types/session-api.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/shared/src/types/session-api.ts) includes optional fields such as `environmentId` and `tags`. When constructing the payload in the bot's session launcher or webhook handler, you can populate these fields to attach metadata that will be persisted alongside the session in the D1 index and available to the running agent in the Modal sandbox.

### Why does the GitHub implementation use `createSessionRuntimeClient` while Slack uses `createSession`?

`createSessionRuntimeClient` is a factory function that returns a session manager instance bound to the current environment and execution context, providing additional methods beyond simple creation. The Slack and Linear bots import a simpler `createSession` helper that wraps this factory for convenience. Both ultimately invoke the same underlying logic in [`packages/control-plane/src/session/runtime-client.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/runtime-client.ts) to instantiate the Session DO and spawn the sandbox.