-
Install the packages
Install undici for HTTP proxying and socks-proxy-agent if you plan to use SOCKS5. Save the examples as
.mjsfiles soimportand top-levelawaitwork without extra configuration.Shell npm install undici socks-proxy-agent -
Send a request through the gateway
Create a ProxyAgent with the gateway address and a Basic Proxy-Authorization token, then pass it to
fetchas the dispatcher. The script prints the exit IP the target sees.JavaScript // check-ip.mjs import { fetch, ProxyAgent } from 'undici'; const dispatcher = new ProxyAgent({ uri: 'http://gw.proxonym.com:8000', token: `Basic ${Buffer.from('USERNAME:PASSWORD').toString('base64')}`, }); const res = await fetch('https://api.ipify.org', { dispatcher }); console.log('Exit IP:', await res.text()); await dispatcher.close(); -
Rotate or keep the same IP
A ProxyAgent reuses its connections, and each connection keeps one exit IP. Open a fresh agent per request to rotate, or reuse one agent with a
-session-ID to keep the same IP for up to 120 minutes.JavaScript // sessions.mjs import { randomBytes } from 'node:crypto'; import { fetch, ProxyAgent } from 'undici'; function proxonymAgent({ country, state, city, asn, session, lifetime = 30 } = {}) { let user = 'USERNAME'; if (country) user += `-country-${country}`; if (state) user += `-state-${state}`; if (city) user += `-city-${city}`; if (asn) user += `-asn-${asn}`; if (session) user += `-session-${session}-lifetime-${lifetime}`; return new ProxyAgent({ uri: 'http://gw.proxonym.com:8000', token: `Basic ${Buffer.from(`${user}:PASSWORD`).toString('base64')}`, }); } // Rotating: a new agent opens a new connection, so each request gets a new IP for (let i = 0; i < 3; i++) { const agent = proxonymAgent({ country: 'us' }); const res = await fetch('https://api.ipify.org', { dispatcher: agent }); console.log('rotating', await res.text()); await agent.close(); } // Sticky: one agent and one session ID keep the same IP for up to 30 minutes const sticky = proxonymAgent({ country: 'us', session: randomBytes(4).toString('hex') }); for (let i = 0; i < 3; i++) { const res = await fetch('https://api.ipify.org', { dispatcher: sticky }); console.log('sticky', await res.text()); } await sticky.close(); -
Target a country, state, city or ASN
Pass location options to the
proxonymAgent()helper from the previous step. Use lowercase ISO country codes and underscores for spaces, for example{ country: 'us', state: 'new_york' }or{ country: 'us', asn: 7922 }.JavaScript const agent = proxonymAgent({ country: 'de', city: 'berlin' }); const res = await fetch('https://ipinfo.io/json', { dispatcher: agent }); const { ip, city, country } = await res.json(); console.log(ip, city, country); await agent.close(); -
Use the SOCKS5 endpoint
SOCKS5 support in undici is still marked experimental, so this example uses socks-proxy-agent with the
httpsmodule; the same agent works with axios and other clients that accept one. Thesocks5h://scheme lets the proxy resolve hostnames.JavaScript // socks5.mjs import https from 'node:https'; import { SocksProxyAgent } from 'socks-proxy-agent'; const agent = new SocksProxyAgent('socks5h://USERNAME-country-gb:[email protected]:1080'); https.get('https://api.ipify.org', { agent }, (res) => { let body = ''; res.on('data', (chunk) => (body += chunk)); res.on('end', () => console.log('Exit IP:', body)); }); -
Retry gateway errors
When the gateway refuses a tunnel,
fetchrejects and the innermosterr.causenames the gateway status. Retry 429, 502 and 504 with a short backoff, and stop on 407, 402 or 403, which point to credentials, balance or a blocked target.JavaScript // retry.mjs import { fetch, ProxyAgent } from 'undici'; const dispatcher = new ProxyAgent({ uri: 'http://gw.proxonym.com:8000', token: `Basic ${Buffer.from('USERNAME-country-us:PASSWORD').toString('base64')}`, }); // The innermost cause carries the gateway status, e.g. "Proxy response (407) !== 200 when HTTP Tunneling" function rootCause(err) { let cause = err; while (cause?.cause) cause = cause.cause; return cause; } async function fetchWithRetry(url, tries = 3) { for (let attempt = 1; ; attempt++) { try { const res = await fetch(url, { dispatcher, signal: AbortSignal.timeout(60_000) }); if ((res.status !== 429 && res.status < 500) || attempt === tries) return res; await res.body?.cancel(); } catch (err) { const reason = rootCause(err).message; console.error(`attempt ${attempt}:`, reason); // 402, 403 and 407 will not go away on a retry if (/\((402|403|407)\)/.test(reason) || attempt === tries) throw err; } await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); } } const res = await fetchWithRetry('https://example.com'); console.log('Status:', res.status); await dispatcher.close();
Frequently asked questions
Can I use the fetch built into Node.js with a proxy?
Yes, in two ways. Recent releases such as Node.js 24 route the global fetch through HTTPS_PROXY when you set NODE_USE_ENV_PROXY=1, which suits a single fixed proxy URL. For control over sessions and locations per request, install undici and import both fetch and ProxyAgent from it, so the client and the dispatcher always share one version.
How do I use Proxonym with axios?
Install https-proxy-agent, create new HttpsProxyAgent("http://USERNAME:[email protected]:8000"), and pass it as httpsAgent with proxy set to false. For SOCKS5, pass a SocksProxyAgent the same way. Each agent pools connections, so create a separate agent for every sticky session you want to run in parallel.
Why do my requests keep the same IP without a session parameter?
The gateway assigns an IP per connection, and undici keeps connections alive, so consecutive requests through one agent to the same host travel through the same tunnel. Close the agent between requests, create a new agent for each request, or add a random session ID per request when every call must exit from a different IP.
What does Proxy response (407) !== 200 when HTTP Tunneling mean?
It is the message undici attaches to a CONNECT request the gateway refused, found on the innermost err.cause, and the number is the gateway status. 407 points to wrong credentials, 402 to an empty balance or an expired plan, and 403 to a target blocked by the acceptable use policy. 502 and 504 are transient, so retry with backoff.
