How to Trigger Agents from Anywhere Using Webhooks and Events

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 (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. 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) 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 (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). 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:

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:

{
  "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:

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

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →