September 20, 2026Mehmet Burak Akgün
Self-Hosted AI Agents: It Runs, Nothing Calls It
Self-hosted AI agents fail at the last mile: the box has no address. 0 of 16 ranking guides mention a webhook. The four ways to fix it. →
The install worked. That is the part every guide covers, and the part that is genuinely easy now. You picked a platform, ran a Docker command, watched the logs settle, opened a browser, and there it was: your own agent, on your own machine, with your own keys in it. Maybe forty minutes, most of it waiting on image pulls.
Then you tried to make it do something while you were not looking, and discovered that nothing in the world can reach it.
This is the part nobody writes about. I searched self hosted ai agents and read the top twenty results, and I will get to exactly what they contain in a minute. The short version is that they will help you choose, and they will help you install, and then they stop at a dashboard on http://your-server-ip:3000 as though opening a browser tab were the goal.
It is not the goal. If the measure of your agent is that you can open it, you have built a chatbot with extra steps.
This article is for the developer or operations engineer who has already installed something, already has it working locally, and now needs an event from the outside world to start a run. If you are still choosing a platform, this is the wrong article and I will point you at the right one at the end.
- Two things get called self-hosting and they have different consequences. Self-hosting the model is a hardware decision. Self-hosting the runtime, which holds your credentials and your logic, is a networking decision, and it is the one that quietly fails
- I fetched and parsed the top-ranking pages for this query. Of the 16 I could read, zero mention a tunnel, zero mention ngrok, and zero contain the word "webhook" even once
- Every page that does discuss remote access gives a human-access answer: an SSH tunnel or a VPN, so that you can reach the box. None of those help when the thing that needs to reach it is Slack
- In our own product the webhook URL shown in the interface is built from
window.location.origin. Open the app at localhost and it hands you a localhost URL to paste into Slack. The interface is not wrong, it is repeating the address you typed- Of the 13 documented entry points into a Heym workflow, 7 require the instance to be reachable from outside and 4 work fine with no address at all. Knowing which is which before you install saves the whole afternoon
- We have 127 documentation pages and, until this article, zero of them explained how to obtain a public hostname. This is a category-wide blind spot and we were in it too
Table of Contents
- What Self-Hosted AI Agents Actually Are
- What the Ranking Guides Stop Short Of
- The Dashboard Was Never the Product
- Pull Triggers and Push Triggers
- The Strongest Objection to All of This
- The URL Your Agent Shows You Is the One You Typed
- Four Ways to Give a Private Box an Address
- The Outbound Tunnel in Practice
- What a Quick Tunnel Will Not Do
- What Has to Arrive With the Hostname
- What We Got Wrong
- What To Do This Week
- Frequently Asked Questions
What Self-Hosted AI Agents Actually Are
Self-hosted AI agents are AI agents whose runtime, state and workflow logic execute on infrastructure you control rather than on a vendor's servers, so the credentials, tool calls and intermediate results stay on your own machine. The model is a separate question. Most teams who describe themselves as self-hosting are running the runtime on their own hardware and still calling a hosted model API, and that is a coherent position rather than a compromise.
Self-hosting an AI agent, in the sense almost everyone means it, is therefore a runtime decision. The same is true whether the product is sold as self-hosted workflow automation or as an agent platform, because underneath both labels sits the same thing: a process on your machine holding your secrets and waiting to be told to do something. We wrote separately about what self-hosting changes for data, credentials and model choice; this article is about the part that comes after all of that is settled.
It is coherent because the runtime is where everything sensitive lives. Your database credentials, your OAuth tokens, your Slack signing secret, the intermediate results of every run, the files the agent produced. A model API call sends a prompt and gets text back. The runtime holds the keys to the building.
That distinction matters more than the usual privacy framing, because the two halves fail in completely different ways. Self-hosting the model is a hardware problem: you need a GPU or you do not. Self-hosting the runtime is a networking problem, and networking problems are the kind that let you get all the way to a working system before revealing themselves.
It is worth remembering how recently this became a normal thing to want. Stanford's AI Index recorded organisational AI use jumping from 55 percent to 78 percent in a single year (Stanford HAI, 2025), which is the kind of curve that takes a practice from research demos to production infrastructure faster than the operational writing can keep up. The tooling matured first, the deployment guidance second, and the networking guidance has not really arrived yet.
The direction of travel here is real and not a hobbyist thing. Broadcom's Private Cloud Outlook 2026 found that 56 percent of enterprises are running or planning to run production inferencing on private cloud against 41 percent on public cloud, with public cloud use for production inference down 15 percentage points year over year (Broadcom, 2026). That is a reversal from near-parity the year before. More of this work is moving onto machines that organisations control, which means more people are about to meet the problem in this article.
What the Ranking Guides Stop Short Of
I wanted to know whether I was imagining the gap, so I measured it instead of asserting it. Everything in this section was collected on 20 September 2026 and anyone can repeat it.
Search for how to build a self hosted AI agent and the results are genuinely useful up to a point. Most open source self-hosted AI agents now ship a Docker Compose file that works on the first try, and a typical self-hosted AI agent Docker setup is three commands and a browser tab. The difficulty has moved somewhere else, and the writing has not moved with it.
I pulled the live results for self hosted ai agents, took the top twenty, and fetched each one with an ordinary browser user agent. Sixteen came back readable. Two did not: one returned sixteen bytes and one returned a blocking page, so I excluded both rather than counting them as evidence in either direction. Then I stripped the markup and counted terms across the remaining sixteen.
| Term | Pages with at least one mention, of 16 |
|---|---|
cloudflared, "Cloudflare Tunnel", trycloudflare | 0 |
ngrok | 0 |
| "webhook" | 0 |
| "public URL" or "public hostname" | 1 |
| port forwarding | 1 |
| Any remote-access term at all | 5 |
Zero of sixteen. Not a single one of the pages currently teaching people to self-host an agent uses the word webhook. Sixteen articles about running software that exists to react to events, and none of them mentions the mechanism by which events arrive.
The five that say anything at all are worth reading closely, because they are not careless. SSD Nodes gives genuinely good security advice: keep every control surface private, bind web interfaces to loopback, and reach them over an SSH tunnel or a VPN. OpenClaw's guide says to avoid public exposure entirely and connect through WireGuard, Tailscale or ZeroTier. Runpod suggests preferring SSH tunnels to public ports during development. Cohorte is the only page that goes public, with an nginx reverse proxy and TLS on a hostname you already own.
Four of those five answers are about you reaching the box. They are correct, and they solve a different problem. An SSH tunnel is a thing a person opens from a laptop. Slack does not have a laptop.
TechRadar publishes the most thorough hands-on guide on the entire page, and it is a good piece of work. It walks through installing the agent, enabling an OpenAI-compatible endpoint, starting the gateway on port 18789, running a web interface in Docker, and connecting the two. It even documents the container-to-host networking fix, --add-host=host.docker.internal:host-gateway, which exists precisely because things cannot reach each other across a boundary. Then it finishes by telling you to open http://your-server-ip:3000, and stops.
There is a structural reason for this, and it is not laziness. Of the twenty pages ranking for this query, twelve are published by a company that sells either the machine or the agent. Every one of them names as "the hard part" whichever layer it happens to sell. The storage vendor says the hard part is ephemeral container storage. The GPU host says it is inference. The VPS companies say it is choosing a VPS. The agent vendors say it is choosing an agent. Nobody sells the network path between your machine and the rest of the world, so nobody writes about it.
The Dashboard Was Never the Product
There is a conversation happening in public about people being tired of interfaces. It usually arrives as a prediction, something about agents replacing software, and it is easy to dismiss as a pitch. Underneath the pitch there is a real observation, and self-hosting is where it becomes concrete.
Watch what these setup guides treat as success. The screenshot at the end is always a browser window. You typed an address, a page loaded, there is a text box, you sent a message, it replied. Congratulations, the guide says, your agent is running.
But that is a demonstration that you can operate it manually. The entire promise of an agent is that it does the work when you are not there. The interesting question is not whether the dashboard loads. It is whether anything happens at three in the morning when an email lands, a deploy fails, or a customer fills in a form.
For that, a human opening a tab is not the input. The input is an event, arriving from a system that has no idea you exist and will not be opening anything. That is why the missing word in all sixteen articles matters so much. A webhook is not a minor integration detail. It is the difference between software that waits for you and software that takes work off you.
The version of "nobody wants to look at a UI" that I believe is narrower and more useful than the slogan: the UI is where you configure the agent, not where you use it. Once it is configured, the measure of the thing is what reaches it while the tab is closed. And a self-hosted runtime with no address is a runtime where nothing ever reaches it, which is a very expensive way to own a chat window.
Pull Triggers and Push Triggers
Here is the distinction that would have saved me an afternoon, and I have not seen it written down anywhere else, so let me be concrete about our own system rather than vague about everyone's.
Heym documents thirteen entry points that can start a workflow. They split cleanly into two groups by direction of travel.
Self-hosted AI agent orchestration is the part of the stack that decides when anything runs at all, so the direction its triggers travel in is the single most useful thing to know about it before you start.
Pull, or outbound. These reach out from your machine and need no address at all. The cron scheduler wakes up and evaluates schedules. The IMAP trigger polls a mailbox. The WebSocket trigger opens an outbound client connection to somebody else's socket. The RabbitMQ consumer pulls from a queue. Four entry points, all of which work perfectly on a laptop behind a router with no configuration whatsoever.
Push, or inbound. These require something on the internet to reach your instance. The generic API entry point at POST /api/workflows/{id}/execute. The MCP endpoints, which is how another agent or an assistant client calls your workflows as tools. The chat portal at /chat/{slug}. The file intake URL. And the three platform webhooks: Slack at /api/slack/webhook/{node_id}, Discord, Telegram. Seven entry points, every one of which is dead on an unreachable box.
Seven of thirteen. Sorted before you install rather than after, that number tells you immediately whether you have a networking task ahead of you or not. If everything you want is a schedule and a mailbox poll, stop reading, you are done, self-hosting really is as easy as the guides say. If you want your agent to answer Slack, you have one more job.
The Strongest Objection to All of This
There is a good counter-argument and it deserves a proper hearing rather than a footnote, because in one important case it defeats the whole premise.
Some platforms let you invert the connection. Slack's Socket Mode is the clearest example. Slack's own documentation describes it as letting an app use the Events API and interactive features "without exposing a public HTTP Request URL", by opening a WebSocket from your app to Slack instead (Slack, 2026). Slack is explicit that it exists for developers behind a corporate firewall or with security concerns that rule out a static HTTP endpoint. That is exactly our situation, and where it is available it is genuinely the better answer.
So why is this article not simply "use Socket Mode"? Three reasons, and I think they are honest ones.
It is per-platform rather than general. Socket Mode solves Slack. It does nothing for a webhook from your payment provider, your CI system, your CRM, or a partner's API, none of which offer an outbound alternative. You would need one bespoke inversion per integration, and most integrations do not have one.
It has to be implemented on both sides. Heym's Slack Trigger is built on the Events API HTTP webhook with signature verification, not on Socket Mode. If your platform has not implemented the socket variant, its availability in the vendor's documentation does not help you.
And it does not cover the other six inbound entry points. An MCP client calling your workflows as tools, a colleague opening the chat portal, a script hitting the execute endpoint: none of those have a socket alternative, because the thing initiating them is not a platform you have an app registered with.
Socket Mode is the right tool when it exists and you can use it. A tunnel is the general case. Where you can use both, use both, and keep the number of things that can reach your instance as small as the job allows.
The URL Your Agent Shows You Is the One You Typed
This is my favourite detail in the whole subject, because it is a small piece of ordinary code that explains the confusion perfectly.
When you drop a Slack Trigger onto a Heym canvas, the properties panel shows you a webhook URL to copy into your Slack app. Here is how that string is built, from usePropertiesPanelController.ts:
const slackTriggerWebhookUrl = computed((): string => {
if (!selectedNode.value || selectedNode.value.type !== "slackTrigger") return "";
return `${window.location.origin}/api/slack/webhook/${selectedNode.value.id}`;
});window.location.origin is whatever address is currently in your browser's address bar. The Discord and Telegram triggers a few lines below are built the same way. There is no configuration lookup, no stored base URL, no guessing.
So if you opened the app at http://localhost:4017, the panel hands you http://localhost:4017/api/slack/webhook/<node-id> and invites you to paste it into Slack. Slack will send a verification challenge to that URL, fail to resolve it, and refuse to save the subscription (Slack, 2026). The field never turns green.
The interface did not lie to you. It repeated the only address it has ever been given, which is the one you typed. Every self-hosted tool that shows you a callback URL has this same property, whether or not it is implemented this literally.
Now the pleasing half. Open that same instance through a public hostname, and the identical line of code renders a reachable URL, because window.location.origin is now the public hostname. No setting changes. No restart. The field that was useless five minutes ago is correct, because the browser is finally looking at the address the rest of the world would use.
That is the whole problem and the whole fix in one expression. The runtime was never broken. It simply had no idea what it was called from outside, and neither did you.
Four Ways to Give a Private Box an Address
There are exactly four shapes of answer here, and the guides that mention any of them mention only one. Choosing badly is not dangerous, it just wastes a day, so here they are side by side with the honest trade-off attached.
| Approach | Gives a public URL | Opens an inbound port | Needs a domain | Good for |
|---|---|---|---|---|
| VPN or mesh network (WireGuard, Tailscale) | No | No | No | You and your colleagues reaching the interface. Useless for third-party webhooks |
| SSH tunnel | No | No | No | One person, one session, debugging. Dies when the laptop sleeps |
| Reverse proxy (nginx, Caddy) | Yes | Yes, 80 and 443 | Yes | A server that already has a public IP and a certificate you are willing to renew |
Outbound tunnel (cloudflared) | Yes | No | Only for a fixed hostname | A machine behind NAT that needs to receive events. The case this article is about |
The first two are the answers the ranking guides give, and within their scope they are right. If the only thing that needs to reach your agent is you, use a VPN, enjoy the excellent security properties of a box with no open ports, and skip the rest of this section.
The third is the classic answer and it is fine when the preconditions hold. A rented VPS comes with a public IP, so a reverse proxy is straightforward there. It is also why VPS-based guides read as easier than laptop-based guides: the address problem was solved by the purchase, quietly, before the tutorial started.
There is a long-standing developer query, how to expose localhost to internet, that has been answered for years in the context of demoing a website to a client. The agent case looks identical and is not. A demo link is temporary and only a human clicks it. A webhook endpoint is registered once with a platform that will keep posting to it for months.
The fourth is the one that fits the case people actually have, which is a machine at home, at the office or on a laptop, behind a router that does network address translation, with no static IP and no ability to open ports even if they wanted to.
The Outbound Tunnel in Practice
The mechanism is worth understanding because it inverts the direction of the connection, which is what makes it work where a reverse proxy cannot.
Cloudflare describes it plainly in its own documentation: Tunnel connects your resources "without a publicly routable IP address", and rather than sending traffic to an external IP, "a lightweight daemon in your infrastructure (cloudflared) creates outbound-only connections to Cloudflare's global network" (Cloudflare, 2026). Your firewall sees an outgoing connection, which it already allows, because it allows every other outgoing connection your machine makes. Nothing is opened inbound. The public hostname lives on Cloudflare's side and traffic arrives back down the pipe you opened.
The throwaway version takes one command and no account at all. Cloudflare's TryCloudflare documentation describes it as generating a random subdomain on trycloudflare.com and proxying requests to your local server (Cloudflare, 2026):
cloudflared tunnel --url http://localhost:4017That prints a random https://<words>.trycloudflare.com hostname and holds the connection open. There is a detail specific to Heym that makes this tidier than it would otherwise be: our deployment docs note that the backend API is served under /api proxied through the frontend container, "so there is only one public-facing port in production". One port, 4017, is the entire surface. You are not tunnelling a stack, you are tunnelling one thing.
Now open the app at the tunnel hostname rather than at localhost, and go back to that Slack Trigger. The webhook URL in the panel is now a trycloudflare.com address. Paste it into your Slack app's Event Subscriptions field, and Slack's verification challenge arrives, gets answered, and the field turns green.
One more step that is easy to miss and annoying to debug. Any self-hosted platform that generates links needs to be told its own public address. In Heym that is FRONTEND_URL, and our own documentation is unambiguous that it is required in production, feeds the OAuth redirect URIs for Google Sheets, BigQuery and Notion, and covers the review, follow-up and file links that background runs mint. It must match the URL users actually use in the browser. Leave it pointing at localhost while serving on a tunnel and the app will appear to work, while every link it hands out points somewhere nobody can go.
View template JSON
{
"heym": true,
"nodes": [
{
"id": "sh_note",
"type": "sticky",
"position": {
"x": 40,
"y": 40
},
"data": {
"label": "setupNote",
"stickyTitle": "Read this before you wire it up",
"stickyColor": "amber",
"stickyWidth": 360,
"stickyHeight": 250,
"note": "This workflow is dead on a localhost install, and that is the point.\n\nThe Slack Trigger panel builds its webhook URL from the address in your browser bar. Open the app at localhost and Slack cannot verify it.\n\nStart the tunnel FIRST, open the app at the tunnel hostname, then copy the webhook URL out of the panel."
}
},
{
"id": "sh_trigger",
"type": "slackTrigger",
"position": {
"x": 60,
"y": 360
},
"data": {
"label": "SlackEvent"
}
},
{
"id": "sh_shape",
"type": "set",
"position": {
"x": 420,
"y": 360
},
"data": {
"label": "ShapeTheRow",
"assignments": "{\"text\": \"$SlackEvent.event.text\", \"user\": \"$SlackEvent.event.user\", \"channel\": \"$SlackEvent.event.channel\", \"at\": \"$SlackEvent.event.ts\"}"
}
},
{
"id": "sh_out",
"type": "output",
"position": {
"x": 780,
"y": 360
},
"data": {
"label": "TrackerRow",
"outputText": "Logged a message from $ShapeTheRow.user in $ShapeTheRow.channel at $ShapeTheRow.at."
}
}
],
"edges": [
{
"id": "sh_e1",
"source": "sh_trigger",
"target": "sh_shape"
},
{
"id": "sh_e2",
"source": "sh_shape",
"target": "sh_out"
}
]
}That shape is not hypothetical. Five of the templates in our own gallery start from a Slack Trigger, including saving Slack requests to Google Sheets and logging Slack messages in BigQuery. Every one of them is inert on an instance Slack cannot reach, and nothing in the template tells you that, which is a gap on our side rather than yours.
What a Quick Tunnel Will Not Do
I want to be careful here, because the five-minute demo is genuinely great and it is also not a deployment, and the difference between those two is where people get burned.
Cloudflare states the limits on quick tunnels directly. They are capped at 200 concurrent in-flight requests, returning a 429 past that. They do not support Server-Sent Events. And Cloudflare is explicit that free tunnels are meant for testing and development rather than for deploying a production website, with no SLA or uptime guarantee (Cloudflare, 2026).
That SSE line has a specific consequence worth knowing before it confuses you. Heym's MCP surface is documented at {origin}/api/mcp/sse and supports two transports: SSE over GET, and Streamable HTTP over POST, the two transports defined in the Model Context Protocol specification (MCP, 2025). On a quick tunnel, the POST transport works and the GET transport does not. An MCP client that negotiates the newer Streamable HTTP transport will connect fine, and one that falls back to SSE will hang in a way that looks like an authentication problem and is not. Live run streaming in the interface is affected by the same limitation.
The hostname is also random and changes every time you restart the daemon, which means every webhook URL you registered becomes wrong the moment your laptop sleeps.
So use the throwaway tunnel for exactly what it is good at: proving in five minutes that the pipeline works end to end, with a real event from a real platform hitting a real workflow on your own machine. Then, before a single colleague depends on it, move to a named tunnel on a domain you own (Cloudflare, 2026). Same daemon, same outbound model, fixed hostname, no concurrency cap, and SSE works.
What Has to Arrive With the Hostname
The moment you point a public hostname at your agent, you have published an API. Not metaphorically. There is now a URL that anyone on the internet can send a request to, and the only question is what happens when they do.
The uncomfortable part is that this is a bigger change for an agent runtime than for an ordinary web app. An agent runtime holds credentials for other systems, executes tool calls, and in many setups can run code. The blast radius of an unauthenticated endpoint here is not "someone reads my data", it is "someone uses my Slack token, my database connection and my cloud account".
Four things that belong in the same sitting as the tunnel, not a follow-up ticket:
Authenticate at the edge. Put an identity layer in front of the hostname so the request is evaluated before it reaches your runtime at all. Cloudflare Access does this on the same hostname you just created. The alternative, relying on nobody guessing a random subdomain, is not a security control. It is a delay.
Never accept a credential from a query string. Execution tokens, share links and webhook secrets belong in headers. Query strings end up in access logs, proxy logs, browser history and Referer headers, and a credential that has been logged is a credential you have to rotate.
Know which of your endpoints allow anonymous execution. In Heym, a workflow's inbound API auth is configured per workflow, with anonymous as one of the options. That setting is entirely reasonable on a LAN and is a decision with a completely different weight once the instance has a public hostname. Go and look at yours the same day.
Verify the signatures that the platforms already give you. Slack signs its events with an HMAC over the signing secret, Discord requires Ed25519 verification of every interaction it sends (Discord, 2026), and Telegram lets you pin a secret token that it echoes back in a header on every update (Telegram, 2026). A well-built trigger checks all of these before running anything. This is free authenticity on your most exposed endpoints and it is worth confirming your platform actually does it rather than assuming.
If you want the fuller version of this argument, what an AI agent sandbox is actually for covers the isolation side, and enterprise AI agents covers what happens when these questions arrive as a procurement checklist instead of a Tuesday afternoon.
What We Got Wrong
What Heym is: Heym is a source-available, self-hosted AI workflow automation platform with a visual canvas for multi-agent pipelines, RAG and MCP. Everything measured about our own product in this article comes from the public repository and the shipped documentation.
It would be convenient to write this as a gap in other people's documentation. It is not.
We ship 127 documentation pages. I grepped every one of them for cloudflared, "cloudflare tunnel", ngrok, "port forwarding", "public hostname", "reverse proxy" and "tailscale". The result was zero across all seven terms.
Our deployment documentation carefully explains that FRONTEND_URL is required in production and must be the public URL of the app, and never once explains how a person is supposed to obtain a public URL in the first place. We documented the variable and skipped the prerequisite. Meanwhile five templates in our gallery start from a Slack Trigger, and none of them mentions that Slack has to be able to reach you.
That is the same blind spot I measured on sixteen competitor pages, sitting in our own repository. It is worth saying plainly, because the pattern is clearly structural rather than anyone's individual oversight. Everyone writing in this category is describing the layer they build, and the network path between the reader's machine and the world is nobody's layer.
Fixing our docs is now an open item. This article is not a substitute for that.
What To Do This Week
If you already run a self-hosted AI agent platform and it has never received an event from outside, the whole exercise is about an hour.
Sort your triggers into pull and push first, because that tells you whether you have any work at all. Schedules, mailbox polls and queue consumers need nothing. Anything involving Slack, an API caller, an MCP client or a chat portal needs an address.
Then start a quick tunnel and open the app through the hostname it gives you rather than through localhost, so that every URL the interface generates is correct from the start. Set the application's public base URL to match. Register one webhook, send one real event, and watch a run appear that you did not start by hand. That moment is the actual finish line of self-hosting, and it is the one no guide puts a screenshot of.
Then go and put authentication in front of it before you close the laptop.
When it is working and someone else is going to depend on it, swap the throwaway tunnel for a named one. And if you are still choosing a platform rather than running one, the n8n alternatives comparison covers the selection question this article deliberately does not, and what is AI workflow automation covers the ground underneath both.
One honest closing thought. Self-hosting is often sold on privacy, and privacy is a real reason. But the reason that has held up best for us is simpler: a runtime you control is a runtime you can point at things. A hosted agent can only reach what its vendor has integrated. Yours can reach a printer on your LAN, a database with no public interface, and a filesystem full of documents you would never upload anywhere. That is worth an afternoon of networking, which is all this ever was.
Frequently Asked Questions
What are self-hosted AI agents?
Self-hosted AI agents are agents whose runtime, state and workflow logic execute on infrastructure you control, rather than on a vendor's servers. The model itself is a separate decision. Most teams self-host the runtime and still call a hosted model API, because the runtime is where the credentials, the tool calls and the business logic live, and that is the part worth keeping. The practical consequence people underestimate is network: a runtime on your own machine has no address the outside world can reach until you give it one.
How do I give a self-hosted AI agent a public URL?
The cheapest reliable way is an outbound tunnel. A small daemon on your machine opens a connection out to a provider's network, and the provider terminates a public hostname on the other end. Cloudflare's tunnel documentation describes it as connecting resources without a publicly routable IP address, using outbound-only connections through your existing firewall. No inbound port is opened, no static IP is needed, and NAT stops mattering. The alternatives are a VPN, an SSH tunnel, or a reverse proxy on a machine that already has a public IP.
Can a self-hosted agent receive webhooks?
Only if the sender can resolve and reach its hostname, which is exactly the part most setup guides skip. Slack, Discord and Telegram all deliver events by posting to a URL you register with them, and Slack additionally sends a verification challenge that must be answered before it will accept the URL at all. A localhost address or a private LAN IP fails that check every time, and the failure looks like a configuration error rather than a networking one.
What is the difference between SaaS and self-hosted agents?
A hosted agent already has an address, a certificate and a team keeping both alive, and in exchange your data and your credentials sit on someone else's machine. A self-hosted agent gives you the data and the credentials and hands you the address problem. That trade is usually worth it, but it is a trade, and the setup guides tend to describe only the half that sounds good.
Do self-hosted AI agents require a GPU?
Not for the agent runtime. Orchestration, tool calls, retries and state are ordinary CPU work and run comfortably on a small machine. A GPU only enters the picture if you also self-host model inference with something like Ollama or vLLM. Many teams self-host the runtime, which is where their data and credentials live, and call a hosted model API for the reasoning.
Can I run a self-hosted AI agent on a cheap VPS?
Yes, and a cheap VPS solves the address problem as a side effect, because it comes with a public IP. That is the real reason VPS guides feel simpler than laptop guides. The trade is that your agent's credentials and its working files now live on a rented machine, and you own the hardening: an unprivileged user, a default-deny firewall, and no control surface exposed raw to the internet.
Are self-hosted AI agents safe to run?
The runtime is as safe as the weakest surface you expose, and giving it a public hostname is the moment that surface appears. Put authentication in front of the hostname rather than relying on the URL being hard to guess, keep credentials in headers rather than query strings, and treat any endpoint that accepts anonymous execution as a public API, because that is what it now is.
Sources
- Broadcom, Private Cloud Outlook 2026, 9 June 2026, production inference on private cloud at 56 percent against 41 percent public, down 15 points year over year
- Cloudflare, Cloudflare Tunnel, the outbound-only connection model and why no inbound port is required
- Cloudflare, TryCloudflare, quick tunnels, the 200 in-flight request cap, and the absence of SSE support
- Cloudflare, Access policies, putting an identity check in front of a hostname
- Slack, Events API, the HTTP request URL and the url_verification handshake
- Slack, Socket Mode, using the Events API without exposing a public HTTP request URL
- Model Context Protocol, Transports, specification 2025-03-26, SSE and Streamable HTTP
- Cloudflare, Create a tunnel, named tunnels on a domain you control
- Discord, Interactions overview, Ed25519 verification of incoming interactions
- Telegram, Bot API, setWebhook and the secret token echoed on every update
- Stanford HAI, AI Index Report 2025, organisational AI use rising from 55 to 78 percent year over year
Steps at a glance
- Separate the model decision from the runtime decision. Write down which one you are actually self-hosting. The runtime holds your credentials, your tool calls and your workflow logic, and it is the part that needs an address. Model inference is a separate choice with separate hardware consequences, and conflating the two is why so many guides start with GPU requirements you may not need.
- List what has to reach the agent before you install anything. Go through your intended triggers and sort them into pull and push. A schedule, a mailbox poll and a queue consumer all reach out from your machine and work fine with no address. A Slack event, a webhook from another system, an MCP client and a chat portal all have to come in, and every one of those needs a resolvable hostname.
- Give the box an address before you wire up any integration. Start the tunnel first, then configure the integrations through the hostname it gives you. Doing it in the other order means every URL you copy out of the interface points at localhost, and you will paste those dead URLs into Slack and Discord before you notice.
- Point the application's own base URL at the hostname. Any self-hosted platform that mints links for OAuth callbacks, review requests or generated files needs to be told its own public address. In Heym that is FRONTEND_URL, and the docs are explicit that it must match the URL users actually type. Leave it at localhost behind a tunnel and the app works while every link it generates points somewhere unreachable.
- Put authentication in front of the hostname on the same day. A public hostname is a public hostname whether or not anyone has guessed it. Add an identity layer at the edge, or at minimum require a header credential on every execution endpoint. Do this in the same sitting as the tunnel, not in a follow-up ticket, because the window between the two is the only genuinely dangerous part of this whole exercise.
- Move from a quick tunnel to a named one before anyone depends on it. A throwaway tunnel is the right tool for proving the idea in five minutes and the wrong tool for anything a colleague will rely on. The hostname is random and changes on restart, and Cloudflare documents concurrency limits and no SSE support on quick tunnels. A named tunnel on a domain you own fixes the hostname and removes both limits.

Co-founder & Engineer
Burak is a co-founder and engineer at Heym, focused on backend infrastructure, the execution engine, and self-hosted deployment. He builds the systems that make Heym's AI workflows run reliably in production.
Reviewed by Ceren Kaya Akgün. Statistics cite named, dated sources, and claims about Heym are verified against the source code before publication. See our editorial policy or report a correction.
Enjoyed this post? Get the next one in your inbox.
A monthly note with practical ideas for building AI workflows that hold up in production. No noise, and you can unsubscribe anytime.