Appearance
Automating Workflows with Events
An agent you have to manually kick off from a chat window isn't automation, it's a slightly fancier way to do something yourself. Real automation means an agent that wakes up because something actually happened: a form was submitted, an email arrived, a webhook fired from another system entirely. This lesson covers wiring that up, and the one architectural wrinkle that trips people up the first time: an agent's reasoning takes seconds to minutes, and most systems sending you a webhook expect a response in milliseconds.

What you'll learn
- n8n vs. a hand-rolled FastAPI endpoint for triggering agent work
- Why a webhook that waits for the agent to finish thinking will time out
- Building a pause for human approval into an otherwise automated workflow
The problem: two very different speeds
A webhook sender, Stripe, a form service, another internal system, expects an HTTP response within a second or two, or it assumes the request failed and retries it, sometimes repeatedly. An LLM agent reasoning through a multi-step task can easily take 10, 30, even 60+ seconds. If your webhook handler tries to do both in one request, receive the event, run the full agent loop, then respond, you'll get timeouts, duplicate triggers from retries, and a system that looks broken even when the agent itself is working fine.
The fix is to decouple the two: accept the event immediately, and do the actual agent work somewhere else, asynchronously.
Picking a trigger tool
| Option | Good for | Costs you | When to pick it |
|---|---|---|---|
| n8n (visual workflow automation) | Non-engineers building/maintaining triggers, hundreds of pre-built integrations | Extra service to run and maintain; UI overhead on very high-volume workloads | You want a visual editor, or need to trigger from a wide variety of third-party services with minimal custom code |
| A FastAPI webhook endpoint | Full control, tight integration with the rest of your Python agent code | You write and maintain the endpoint yourself | You're comfortable in code and don't need n8n's broader integration library |
Either works. This course uses FastAPI directly for tight integration with the agent code from earlier lessons, but n8n is a legitimate choice if a visual workflow tool fits your team better.
Build it: accept fast, work in the background
To handle event webhooks reliably without causing timeouts, implement an asynchronous handler using FastAPI's BackgroundTasks. This lets you respond immediately while running the agent execution in the background:
python
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
@app.post("/agent/triage")
async def triage_webhook(payload: dict, background_tasks: BackgroundTasks):
# Respond immediately, this is what keeps the sender from timing out or retrying
background_tasks.add_task(run_agent_triage, payload)
return {"status": "accepted"}, 202
def run_agent_triage(payload: dict):
# The actual agent loop runs here, off the request/response cycle entirely
result = run_agent(f"Triage this incoming ticket: {payload}")
save_result(result)Returning 202 Accepted immediately and doing the real work in a background task is the whole trick, the sender gets its fast response, and the agent gets as much time as it actually needs.
If your automation tool runs in a separate Docker container from your Python agent code (n8n in one container, your FastAPI service on the host), you'll need host.docker.internal in the n8n workflow's webhook URL to reach the host-native service, a container's localhost refers to itself, not the host machine.
Pausing for a human, mid-workflow
Not every action should happen unattended. For anything consequential, deleting data, sending a customer-facing message, spending money, build in a pause that waits for an explicit human approval before the agent continues:
python
@app.post("/agent/high-risk-action")
async def request_approval(action: dict):
action_id = save_pending_action(action) # persisted, not just in memory
notify_human_for_approval(action_id, action)
return {"status": "pending_approval", "action_id": action_id}
@app.post("/agent/approve/{action_id}")
async def approve_action(action_id: str):
action = load_pending_action(action_id)
execute_action(action)
return {"status": "executed"}Persisting the pending action (to a database, not just an in-memory variable) matters, if your service restarts while an approval is still pending, an in-memory-only version loses the request entirely.
What goes wrong
| Mistake | How you notice it | The fix |
|---|---|---|
| Running the agent inline in the webhook handler | Webhook sender times out or retries, sometimes triggering the agent multiple times for one event | Accept the event immediately, run the agent in a background task |
| Container can't reach the host agent service | Connection refused from inside a Docker-based automation tool like n8n | Use host.docker.internal (or your platform's equivalent) instead of localhost |
| Pending approvals stored only in memory | An approval request vanishes if the service restarts before a human responds | Persist pending actions to a database, not an in-process variable |
| No error handling on the background task | An agent that fails mid-task fails silently, no error state, no log signal | Wrap the background task in error handling that logs and records a failure state, not just a bare exception |
Confirm it worked
To verify that your background task execution is working properly and not blocking the request thread, measure the response time of your webhook handler using time curl:
bash
# Time the webhook response, it should return in well under a second,
# regardless of how long the agent's actual work takes afterward
time curl -X POST http://localhost:8000/agent/triage -d '{"ticket": "test"}'Then confirm the background work actually completed by checking your logs or database a few seconds later for the agent's result, the fast response and the eventual real result are two separate things, and both need to be true.
Next Step: proceed to Running Agent Code Safely to learn how to safely run the code your agents generate.