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:
- Extracts the
thread_idfrom the event payload - Retrieves the Thread from
ThreadStoreusing the ID - Validates that the Thread is awaiting human input before appending the response
- Re-invokes
agentLoopto 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
Threadclass to maintain chronological event history that survives across HTTP requests - Webhook ingress: Implement a
/webhookendpoint inserver.tsto receive external events from any HTTP-capable system - Resumable loops: Structure
agentLoopto handle pauses for human input without losing context - Pluggable storage: Replace
ThreadStoreinstate.tswith 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →