# How to Trigger Agents from Anywhere Using Webhooks and Events

> Learn to trigger agents from anywhere using webhooks and events with the 12-Factor Agents framework. Easily integrate external systems for seamless agent execution.

- Repository: [HumanLayer/12-factor-agents](https://github.com/humanlayer/12-factor-agents)
- Tags: how-to-guide
- Published: 2026-05-19

---

**The 12-Factor Agents framework enables any external system to trigger agent execution by exposing an HTTP webhook endpoint that receives events and routes them into a persistent Thread-based execution loop.**

The `humanlayer/12-factor-agents` repository demonstrates how to build agents that respond to events from Slack, email, SMS, cron jobs, or custom services. By decoupling the trigger mechanism from the agent's core logic, you create a system where any HTTP-capable service can initiate or resume agent workflows while maintaining deterministic, testable execution.

## Understanding the Thread Model and State Persistence

The foundation of triggerable agents rests on the **Thread** abstraction. In [`workshops/2025-05/sections/12-humanlayer-webhook/src/agent.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/workshops/2025-05/sections/12-humanlayer-webhook/src/agent.ts) (lines 8-33), the `Thread` class maintains a chronologically ordered list of events—user inputs, tool calls, and human responses—that serve as the LLM's context window.

Persistence is handled by the **ThreadStore** interface defined in [`workshops/2025-05/sections/12-humanlayer-webhook/src/state.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/workshops/2025-05/sections/12-humanlayer-webhook/src/state.ts). This provides simple `create`, `get`, and `update` methods backed by an in-memory `Map` by default. You can swap this implementation for Redis, PostgreSQL, or any external database without modifying the agent logic, ensuring your agents remain stateless processes that scale horizontally.

## Implementing the Agent Loop

The **agentLoop** function (lines 84-108 in [`workshops/2025-05/sections/12-humanlayer-webhook/src/agent.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/workshops/2025-05/sections/12-humanlayer-webhook/src/agent.ts)) orchestrates execution. It serializes the current Thread, invokes the LLM to determine the next step using `b.DetermineNextStep`, and records the resulting tool call.

The loop handles three outcomes:
- **Immediate execution**: Runs deterministic tools like calculations or API calls
- **Human-in-the-loop pause**: Creates a HumanLayer contact and returns the Thread with a `response_url`
- **Completion**: Returns the final answer when the LLM signals done

This design ensures the agent can pause indefinitely for human input and resume exactly where it left off when the webhook receives a response.

## Receiving External Events with Webhook Endpoints

To trigger agents from anywhere, expose a POST endpoint at `/webhook` as implemented in [`workshops/2025-05/sections/12-humanlayer-webhook/src/server.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/workshops/2025-05/sections/12-humanlayer-webhook/src/server.ts) (lines 73-86). This route accepts payloads from external systems containing a `V1Beta2HumanContactCompleted` event.

The webhook handler performs four critical operations:
1. Extracts the `thread_id` from the event payload
2. Retrieves the Thread from `ThreadStore` using the ID
3. Validates that the Thread is awaiting human input before appending the response
4. Re-invokes `agentLoop` to continue processing with the new context

This mechanism allows Slack notifications, email replies, or SMS responses to seamlessly resume agent execution without maintaining persistent connections.

## Human-in-the-Loop Integration

When the agent requires clarification or approval, the server creates a HumanLayer contact via `hl.createHumanContact` in the `/thread` handler (lines 45-57 in [`server.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/server.ts)). The system stores the response URL (`/thread/:id/response`) on the last event, enabling external channels to post answers back to the specific Thread.

The response URL pattern ensures that asynchronous human interactions—whether from email threads or Slack DMs—correctly map to their originating agent context. This creates a bidirectional bridge between synchronous HTTP APIs and asynchronous human communication channels.

## Code Examples and Workflow

Start a new agent thread from any HTTP client:

```bash
curl -X POST http://localhost:3000/thread \
     -H "Content-Type: application/json" \
     -d '{"message":"What is 7 × 3?"}'

```

The server responds with a Thread ID and event history:

```json
{
  "thread_id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
  "events": [
    {"type":"user_input","data":"What is 7 × 3?"},
    {"type":"tool_call","data":{"intent":"multiply","a":7,"b":3}},
    {"type":"tool_response","data":21,"response_url":"/thread/a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6/response"}
  ]
}

```

When the agent requires human clarification, the server automatically sends a HumanLayer contact. Resume execution by posting the human response:

```bash
curl -X POST http://localhost:3000/webhook \
     -H "Content-Type: application/json" \
     -d '{
           "event": {
             "status": {"response":"yes"},
             "spec": {"state":{"thread_id":"a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6"}}
           }
         }'

```

## Summary

- **Thread-based state**: Use the `Thread` class to maintain chronological event history that survives across HTTP requests
- **Webhook ingress**: Implement a `/webhook` endpoint in [`server.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/server.ts) to receive external events from any HTTP-capable system
- **Resumable loops**: Structure `agentLoop` to handle pauses for human input without losing context
- **Pluggable storage**: Replace `ThreadStore` in [`state.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/state.ts) with persistent backends like Redis for production deployments
- **Response URLs**: Store callback URLs in Thread events to map asynchronous human responses back to active agent contexts

## Frequently Asked Questions

### What is a Thread in the 12-Factor Agents framework?

A **Thread** is a state container that holds a chronologically ordered list of events including user inputs, tool calls, and human responses. According to the source code in [`workshops/2025-05/sections/12-humanlayer-webhook/src/agent.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/workshops/2025-05/sections/12-humanlayer-webhook/src/agent.ts), the Thread serves as the LLM's context window and can be serialized to any persistence layer, enabling agents to resume execution after arbitrary delays.

### How does the webhook endpoint resume agent execution?

The `/webhook` handler in [`server.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/server.ts) extracts the `thread_id` from incoming POST payloads, retrieves the corresponding Thread from `ThreadStore`, appends the human response as a new event, and re-invokes `agentLoop` with the updated state. This process allows external systems like email services or Slack to trigger agent continuation without maintaining long-lived connections.

### Can I replace the in-memory ThreadStore with Redis or PostgreSQL?

Yes. The `ThreadStore` interface defined in [`workshops/2025-05/sections/12-humanlayer-webhook/src/state.ts`](https://github.com/humanlayer/12-factor-agents/blob/main/workshops/2025-05/sections/12-humanlayer-webhook/src/state.ts) provides a simple abstraction with `create`, `get`, and `update` methods. The default implementation uses an in-memory `Map`, but you can substitute Redis, SQLite, or any database implementation that satisfies the interface contract.

### What external systems can trigger agents via webhooks?

Any system capable of making HTTP POST requests can trigger agents, including Slack slash commands, email services (via inbound webhooks), SMS gateways, cron job schedulers, CI/CD pipelines, and custom microservices. The framework specifically handles `V1Beta2HumanContactCompleted` events from HumanLayer, but the `/webhook` endpoint pattern supports arbitrary event schemas.