Webhooks and SSE for Long-Running AI Research Jobs
TL;DR:
- Webhooks deliver the finished result: the job runs asynchronously, then POSTs the result to a callback URL you registered when you started it. They are ideal for completion events, retries, and fan-in from parallel subtasks.
- SSE (Server-Sent Events) streams progress: the server holds an HTTP connection open and pushes progress events and partial results down it as they appear. One-way, server to client, built for live updates.
- Parallel research jobs combine them: a coordinator fans out subtasks, each worker streams progress over SSE, and results fan back in through a shared callback — or per-task callbacks with correlation IDs.
- Both approaches require a reachable receiving path. A conventional webhook needs a public callback. When both endpoints are agents, a stable overlay address can replace that public-URL assumption; a conventional SaaS sender still needs an HTTP gateway or public receiver.
If you are building a research system where jobs run for minutes to hours — deep research, document analysis, codebase investigation — you will eventually ask how parallel task webhooks and SSE streaming work for long-running research jobs. The short answer: webhooks deliver the finished result to a callback URL, SSE streams progress over a connection the server keeps open, and production systems usually combine both. The longer answer is about fan-out, retries, idempotency, and one requirement both approaches quietly share: something has to be reachable at the end of the call.
This post walks through the mechanics of each pattern, how they behave under parallel task fan-out, where they break in agent-based systems, and what a persistent delivery layer changes.
Table of Contents
- What webhooks do for long-running jobs
- How SSE streaming works for long-running research jobs
- Webhooks vs SSE vs polling for parallel research tasks
- Where webhooks and SSE break for agent-based research systems
- The alternative: deliver results to a stable agent address
- Frequently asked questions
What webhooks do for long-running jobs
A webhook is a callback over HTTP. The flow looks like this: your client submits a job to a worker or job API and gets back a job ID. The worker runs the job asynchronously — this is the part that takes minutes or hours. When the job finishes, the worker makes an outbound POST to the callback URL you supplied at submit time, with the result in the body. Your side acknowledges the POST, and the job is complete.
Three details make webhooks work in practice:
- Authentication. The callback URL carries a token, or the request includes a signature the receiver can verify. Without one, anyone who learns the URL can POST fake results.
- Retries with backoff. Delivery is best-effort. If the POST fails, the worker retries with exponential backoff. The receiver must be idempotent — the same result arriving twice should be harmless, so results carry a job ID or idempotency key.
- Timeout handling. The callback should complete quickly. If the receiver is slow, the worker may retry or give up; long processing belongs in the job, not in the callback handler.
Parallel tasks: per-task callbacks or fan-in
When a research job fans out into parallel subtasks, you have two designs. In per-task callbacks, each subtask POSTs its own result to the callback URL. The receiver correlates results by subtask ID and waits until the expected set arrives. This is simple but chatty, and results arrive out of order — ordering must be reconstructed by the receiver.
In fan-in, subtasks report to a coordinator inside the job, and the coordinator POSTs one aggregate callback when the whole job completes. Fewer endpoints, deterministic ordering, one place to implement retries and idempotency. Most production research pipelines use fan-in with a per-subtask progress channel on top — which is where SSE enters.
How SSE streaming works for long-running research jobs
Server-Sent Events is the standard way to stream one-way updates over plain HTTP. The client opens a GET request with Accept: text/event-stream. The server keeps the connection open and writes events as frames: event: names the event type, data: carries the payload, id: marks the position in the stream, and comment lines (starting with a colon) act as heartbeats to keep intermediaries from closing idle connections.
Two properties make SSE attractive for research workloads:
- Automatic reconnection. If the connection drops, the browser or client reconnects on its own and sends
Last-Event-ID, letting the server resume from where the client left off. - No polling. Progress arrives when it happens — stage changes, sources found, tokens generated, partial results — without the client repeatedly asking.
SSE is one-way by design: server to client. The client cannot push messages back over the same connection; it uses ordinary requests for that. And SSE is still HTTP — it runs through reverse proxies and load balancers, which means those layers must be configured for long-lived connections: buffering off, read and idle timeouts raised, and connection limits accounted for. Each open SSE connection occupies a socket on both ends for the entire job.
In a parallel research job, SSE typically carries progress from each worker to the coordinator, while the final result arrives through the webhook path. The two patterns complement each other: SSE for the live view, webhook for the authoritative completion signal.
Webhooks vs SSE vs polling for parallel research tasks
| Mechanism | Best for | What it assumes |
|---|---|---|
| Polling | Short jobs, simple clients, no push infrastructure | The client can keep asking; wasted requests are acceptable |
| Webhook | Completion of long jobs, fan-in from parallel subtasks | The callback receiver is publicly reachable and the URL survives the job |
| SSE | Live progress, partial results, dashboards | The connection stays open; proxies and load balancers cooperate |
| Hybrid (SSE + webhook) | Production research pipelines | Both of the above, plus a coordinator to join the two paths |
The table makes the tradeoff visible: the more live feedback you want, the more infrastructure you need to keep connections and endpoints healthy. For long-running research jobs, the hybrid is the common answer — and its failure modes are almost never in the transport choice.
Where webhooks and SSE break for agent-based research systems
Every pattern above shares one assumption: the receiving end is reachable at a stable URL. That assumption fails in specific, predictable ways:
- NAT and firewalls. A webhook is an inbound connection from the worker to your callback URL. An agent running on a laptop, behind a home router, or in a container with no public IP cannot accept that connection unless something — port forwarding, a reverse proxy, a public host — stands in front of it.
- Restarts. The callback URL dies with the process that hosts it. If the receiver restarts mid-job, retries hit nothing until the endpoint is back — and a fresh process often means a fresh address.
- Ephemeral addresses. VMs and containers get new IPs on every deployment. The URL you registered at submit time may not be yours by completion time.
- Ordering and correlation. With per-task callbacks, results arrive out of order. Correlation IDs and idempotency keys are mandatory, and the receiver has to reconstruct the expected set.
- Attack surface. A public callback endpoint is an open door. Anyone who learns the URL can POST fabricated results, so tokens or signatures are not optional.
None of this is a criticism of webhooks or SSE — they are the right tools inside a trusted boundary with reachable endpoints. The problems start exactly where agent-based systems live: distributed, behind NAT, and restarted without ceremony.
The alternative: deliver results to a stable agent address
If both sides of the exchange are agents, an overlay can remove the need to expose the receiving agent as a public HTTP server. Pilot Protocol gives a node a persistent virtual address that remains its reconnection target across daemon restarts and network-path changes. Traffic uses encrypted UDP tunnels, with STUN discovery, hole-punching, and relay fallback when a direct path is unavailable.
The research pipeline looks different on this layer. The coordinator registers its stable address and workers resolve that address before connecting. Progress and results can travel over the agent channel without publishing a callback URL. The address is durable; an individual connection is not, so applications still need correlation IDs, idempotency, retry, and resume behavior when either process or path restarts. Admission is governed by bilateral trust or explicit network membership policy rather than possession of a public callback URL.
Discovery is part of the same layer. A rendezvous registry lets agents find each other by name or tag, so the coordinator does not need to hand out URLs — workers resolve the agent they are working for. For builders, the Pilot app store adds installable capability apps — grounded search, web-to-markdown, runtime security — that run locally on the daemon, discovered and installed with a single command.
For a deeper look at when replacing webhooks with persistent tunnels makes sense, see network tunnels for AI agent communication. For how this layer compares with the HTTP+SSE transport that MCP tunnels use, see MCP tunnels vs VPN for AI agents.
Get started with one command:
curl -fsSL https://pilotprotocol.network/install.sh | sh
Frequently asked questions
What is the difference between a webhook and SSE?
A webhook is a one-way HTTP POST sent by the server to a callback URL when something completes — one request, one response. SSE is a long-lived HTTP connection over which the server pushes many events over time. Webhooks answer "is it done?", SSE answers "what is happening right now?"
Can webhooks handle parallel tasks?
Yes, in two shapes: per-task callbacks (each subtask POSTs its own result, correlated by subtask ID) or fan-in (subtasks report to a coordinator that POSTs one aggregate callback when the whole job finishes). Fan-in is simpler to make idempotent and ordered.
Is SSE good for delivering the final result of a long job?
SSE is designed for live progress and partial results. Delivering the authoritative final result over a long-lived connection is risky — proxies and load balancers can close idle or long-lived connections, and a dropped connection after an hour of work is a bad place to lose the result. Most systems stream progress over SSE and deliver the final result over a webhook or a dedicated fetch.
Why do webhooks fail for agents behind NAT?
A webhook delivery is an inbound connection: the worker initiates a connection to your callback URL. Behind NAT, inbound connections cannot reach the agent unless port forwarding or a reverse proxy is configured. An agent-native overlay with NAT traversal removes this requirement — the tunnel is established from the agent's side and inbound traffic arrives through it.
Does Pilot Protocol replace webhooks and SSE?
No. Webhooks and SSE remain useful when an HTTP endpoint is reachable. For agent-to-agent delivery, Pilot provides a stable address and an encrypted path across NAT; after a restart the path is re-established rather than magically preserved. A conventional SaaS webhook sender still needs an HTTP gateway or public receiver.
What is a fan-in callback?
A fan-in callback is a single webhook posted after a parallel job completes: subtasks report their results to a coordinator inside the job, the coordinator aggregates them, and one POST carries the combined result to the callback URL. It reduces endpoint churn, makes ordering deterministic, and centralizes retry and idempotency logic.

