Skip to content

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.

Gnome mascot

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

OptionGood forCosts youWhen to pick it
n8n (visual workflow automation)Non-engineers building/maintaining triggers, hundreds of pre-built integrationsExtra service to run and maintain; UI overhead on very high-volume workloadsYou want a visual editor, or need to trigger from a wide variety of third-party services with minimal custom code
A FastAPI webhook endpointFull control, tight integration with the rest of your Python agent codeYou write and maintain the endpoint yourselfYou'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

MistakeHow you notice itThe fix
Running the agent inline in the webhook handlerWebhook sender times out or retries, sometimes triggering the agent multiple times for one eventAccept the event immediately, run the agent in a background task
Container can't reach the host agent serviceConnection refused from inside a Docker-based automation tool like n8nUse host.docker.internal (or your platform's equivalent) instead of localhost
Pending approvals stored only in memoryAn approval request vanishes if the service restarts before a human respondsPersist pending actions to a database, not an in-process variable
No error handling on the background taskAn agent that fails mid-task fails silently, no error state, no log signalWrap 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.