The basics
Node.js doesn't apply a proxy automatically, so you attach one through an agent or dispatcher. With Webshare, you choose a proxy URL first:
- Backbone:
http://USERNAME-rotate:PASSWORD@p.webshare.io:80(rotate per request) or a session username for stickiness. - Direct:
http://USERNAME:PASSWORD@IP:PORTfrom your proxy list.
Keep credentials in environment variables. General background in Node.js and axios proxies.
undici and the built-in fetch
import { fetch, ProxyAgent } from "undici";
const proxy = `http://${process.env.WS_USER}-rotate:${process.env.WS_PASS}@p.webshare.io:80`;
const dispatcher = new ProxyAgent(proxy);
const res = await fetch("https://httpbin.org/ip", { dispatcher });
console.log(await res.json());Create the agent once and reuse it; making a new agent per request wastes connections.
axios
axios's own proxy option doesn't handle HTTPS tunnelling well. Use an agent instead:
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
const agent = new HttpsProxyAgent(`http://${user}-rotate:${pw}@p.webshare.io:80`);
const { data } = await axios.get("https://httpbin.org/ip", { httpsAgent: agent, proxy: false, timeout: 30000 });
console.log(data);Setting proxy: false stops axios from also reading proxy environment variables.
got
import got from "got";
import { HttpsProxyAgent } from "https-proxy-agent";
const agent = { https: new HttpsProxyAgent(`http://${user}:${pw}@${ip}:${port}`) };
const body = await got("https://httpbin.org/ip", { agent, timeout: { request: 30000 } }).json();Rotating through a direct list
import fs from "node:fs";
import { ProxyAgent, fetch } from "undici";
const agents = fs.readFileSync("webshare_proxies.txt", "utf8").trim().split("\n").map((l) => {
const [ip, port, u, p] = l.split(":");
return new ProxyAgent(`http://${u}:${p}@${ip}:${port}`);
});
let i = 0;
const next = () => agents[i++ % agents.length];
for (const url of urls) {
const res = await fetch(url, { dispatcher: next() });
console.log(url, res.status);
}Refresh the list from the Webshare API periodically so replaced proxies are picked up.
Sticky sessions
Give each logical session its own agent with a session username, and keep cookies with it:
const sid = Math.floor(Math.random() * 1e6);
const sticky = new ProxyAgent(`http://${user}-${sid}:${pw}@p.webshare.io:80`);Budget tip: You can test most of this guide for free: Webshare gives every new account 10 datacenter proxies and 1 GB of bandwidth a month without a credit card. Move to a paid plan once the pilot works. See our complete Webshare guide.
SOCKS5
import { SocksProxyAgent } from "socks-proxy-agent";
const agent = new SocksProxyAgent(`socks5h://${user}:${pw}@${ip}:${port}`);Use socks5h so DNS resolves through the proxy.
Concurrency
Use a limiter so you stay under your plan's thread count:
import pLimit from "p-limit";
const limit = pLimit(40);
const results = await Promise.all(urls.map((u) => limit(() => fetch(u, { dispatcher }))));See Webshare bandwidth and threads.
Error handling
407: credentials or mode mismatch. Test with cURL first.ECONNRESETor timeouts at high load: often the thread limit. Lower concurrency.403or429from the target: a block or rate limit. Slow down, rotate, or move the target to residential.
More in fixing common Webshare errors.
A small production-ready wrapper
import { fetch, ProxyAgent } from "undici";
const agents = new Map();
function agentFor(session) {
const user = `${process.env.WS_USER}-${session ?? "rotate"}`;
const key = user;
if (!agents.has(key)) agents.set(key, new ProxyAgent(`http://${user}:${process.env.WS_PASS}@p.webshare.io:80`));
return agents.get(key);
}
export async function get(url, { session, tries = 3 } = {}) {
for (let i = 0; i < tries; i++) {
try {
const res = await fetch(url, { dispatcher: agentFor(session), signal: AbortSignal.timeout(30000) });
if ([403, 429, 503].includes(res.status)) throw new Error(`blocked ${res.status}`);
return res;
} catch (e) {
await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
}
}
throw new Error(`failed ${url}`);
}Agents are cached per session, so sticky flows reuse one agent while rotating calls share the rotate agent.
Keep-alive and rotation
undici keeps connections alive by default, which is good for speed but means a rotating gateway may keep the same exit IP for several requests. When you need a guaranteed new IP each time, create a short-lived agent per request and close it afterwards, or use a direct list and pick a different proxy per request.
Proxy tip: If your residential usage goes up and down, compare Geonode: its per-GB residential bandwidth doesn't expire, so you only pay for what you use. See using Geonode with Node.js.
Memory and agent hygiene
Creating thousands of agents without closing them leaks sockets. Cache agents by key, close unused ones (await agent.close()), and cap the cache size in long-running services.
Choosing products from Node.js services
Backend services that call partner APIs often need a fixed IP the partner can whitelist; a dedicated datacenter or static residential IP is ideal. Scrapers use datacenter lists for lenient targets and rotating residential for protected ones.
A real-world example
A Node.js backend service checks product availability on partner sites for an e-commerce app. Partners whitelist the service's IP, so the team buys two dedicated datacenter proxies from Webshare and sends partner API calls through them with an undici ProxyAgent, giving partners a stable IP even as the service's own servers scale up and down. Separately, a scraping worker uses the backbone gateway with the rotate option for public product pages. Each use case gets its own agent and credentials.
If you're still shortlisting, compare this setup with the options in our best proxies guide, and use our cheap proxies guide to estimate what a sensible budget looks like.
Key takeaways
- Node.js needs an agent or dispatcher for proxies: undici
ProxyAgent,https-proxy-agentorsocks-proxy-agent. - Reuse agents, and close ones you no longer need to avoid socket leaks.
- Keep-alive can keep the same exit IP on a rotating gateway; create new agents when you need a fresh IP.
- Cap concurrency with a limiter below your plan's thread limit.
- Dedicated proxies give partner APIs a stable IP to whitelist.
FAQ
Does Node's built-in fetch support proxies?
Through undici's ProxyAgent, passed as the dispatcher option.
Which agent library should I use?
undici for fetch, https-proxy-agent for axios and got, and socks-proxy-agent for SOCKS5.
Can I use Webshare with Puppeteer from Node.js?
Yes; see Webshare with Puppeteer.
Why use a proxy for partner API calls?
A dedicated proxy gives partners one stable IP to whitelist, even when your servers change.
Does axios support Webshare proxies?
Yes, through https-proxy-agent passed as httpsAgent, with axios's own proxy option set to false.
How do I avoid socket leaks with many proxy agents?
Cache agents by proxy URL, reuse them, and close agents you no longer need.
Editorial note: Proxy providers change their pools, features and prices often, so treat any figures here as typical ranges at the time of writing and confirm current terms with the provider before you buy.