# Ludax proxies in Puppeteer: guide for AI assistants

> Give this file to your AI assistant (ChatGPT, Claude, Cursor, Copilot) and it can write working Puppeteer code for Ludax Proxy. Human-readable version: https://ludaxproxy.com/docs/puppeteer

You are helping a developer route Puppeteer (headless Chrome) through Ludax residential proxies, which need a username and a password. Follow the rules below exactly; they come from the gateways' behaviour and from tests with Puppeteer 25.16 and Chrome (2026-11-10).

## Rules

- Launch Chrome with `--proxy-server=http://gw.ludaxproxy.com:7777` (host and port only), then call `page.authenticate({ username, password })` on the page **before** its first `goto()`. Chrome's flag takes no credentials: putting them in the flag fails with `net::ERR_NO_SUPPORTED_PROXIES`.
- One proxy per context: `browser.createBrowserContext({ proxyServer: "http://gw.ludaxproxy.com:7777" })`, then `page.authenticate()` on each of its pages. It needs no proxy at launch.
- **Give each context its own session token** (`-session-<TOKEN>-time-<MINUTES>`, up to 1000 minutes). A page is many connections; without a token each can leave from a new IP. A new context with a new token is how to rotate.
- Use the standard HTTP gateways (Residential `gw.ludaxproxy.com:7777`, Premium `gw.ludaxproxy.com:8080`). Do **not** use the TLS gateways with a browser: Chrome already sends a real browser handshake. Chrome does not send a login to SOCKS5 proxies: use the HTTP gateway.
- Match the browser to the exit: `--lang=de-DE`, `page.emulateTimezone("Europe/Berlin")` and an `Accept-Language` header with `-country-de`.
- Save bandwidth (plans are priced per GB): `page.setRequestInterception(true)` and abort `image`, `font` and `media`; it works together with `authenticate()`.
- **Errors:** a refused login does **not** throw: `goto()` returns a response with `status() === 407` (Chrome's "This page isn't working" page), so check the status and never retry it (30 failed logins in 5 minutes block the client IP for 10 minutes). `net::ERR_TUNNEL_CONNECTION_FAILED` is a gateway 502 (retry with backoff). `net::ERR_INVALID_AUTH_CREDENTIALS` means `authenticate()` was not called. `net::ERR_PROXY_CONNECTION_FAILED` means the gateway was not reached.
- puppeteer-extra (stealth plugin) takes the proxy the same way.

## Credentials

- **Username:** the product prefix (`resi-` or `premi-`), then the developer's account name from the dashboard (Generate), for example `resi-lx_k3m9q2p7xa`. The account name is `lx_` and 10 characters, the same for every plan. Never invent one: ask the developer, or use a placeholder such as `YOUR_USERNAME`.
- **Password:** the plan's own password (16 hexadecimal characters, one per plan), then the options, in this order: `-session-<TOKEN>-time-<MINUTES>`, then `-country-<cc>`, `-state-<code>`, `-city-<name>` (and `-asn-<number>`, `-os-<device>` on Premium). Use a placeholder such as `YOUR_PASSWORD`, or read it from an environment variable. Never put options in the username.
- **Sessions:** without `-session-` every new connection leaves from a new IP. `-session-<TOKEN>-time-<MINUTES>` keeps one IP for every connection that sends the same token (TOKEN: 3 to 24 letters or digits; MINUTES: 1 to 1000; Premium holds a session at least 3 minutes). Always send `-time-`: without it a session lasts 1 minute. On the TLS gateways add `-sameip` after the time, or only the browser profile is held.
- **Location:** `-country-` takes a lower-case ISO code (`us`, `de`, `jp`); `-state-` and `-city-` follow it (`-country-de-state-by-city-munich`); on Premium the state is its name without spaces (`-country-us-state-illinois-city-chicago`), in any of 219 countries. `-os-` (Premium) takes `windows`, `android`, `ios` or `macos`. With no free exit in that city or region the request falls back to the country. Codes for every country: https://ludaxproxy.com/locations
- **The port picks the product:** a plan's password works only on its own product's gateway, and the prefix must match it.

## Products and gateways in force

| Product | Username prefix | HTTP and HTTPS | SOCKS5 | Browser TLS | Sticky session |
| --- | --- | --- | --- | --- | --- |
| Residential | `resi-` | `gw.ludaxproxy.com:7777` | `gw.ludaxproxy.com:7575` | No | up to 1000 min |
| Residential TLS | `resi-` | `gw.ludaxproxy.com:9999` | none | Yes | up to 1000 min |
| Residential Premium | `premi-` | `gw.ludaxproxy.com:8080` | `gw.ludaxproxy.com:1080` | No | up to 1000 min |
| Residential Premium TLS | `premi-` | `gw.ludaxproxy.com:8081` | none | Yes | up to 1000 min |

## Check the connection

`GET https://api.ludaxproxy.com/whoami` through the proxy returns JSON: `ip`, `country` (full name), `type`, `tls_profile` (the browser the TLS handshake matches) and `latency_ms`. Add `?full=1` for a `debug` block with the JA4 fingerprint, the HTTP/2 fingerprint, the ASN and the User-Agent that arrived.

## Tested examples

The same code as on https://ludaxproxy.com/docs/puppeteer. Replace `lx_k3m9q2p7xa` and `YOUR_PASSWORD` with the developer's credentials.

### Install

```bash
npm install puppeteer
```

### First request

```javascript
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({
  args: ["--proxy-server=http://gw.ludaxproxy.com:7777"],
});
const page = await browser.newPage();
// the proxy login: Chrome's flag takes no username or password
await page.authenticate({ username: "resi-lx_k3m9q2p7xa", password: "YOUR_PASSWORD-country-us" });

const response = await page.goto("https://api.ludaxproxy.com/whoami");
console.log(response.status(), await page.$eval("body", (body) => body.innerText));
await browser.close();
```

### One IP per browser context (rotation by context)

```javascript
import puppeteer from "puppeteer";
import { randomBytes } from "node:crypto";

const browser = await puppeteer.launch();
for (const job of ["account-1", "account-2", "account-3"]) {
  // each context has its own proxy and session token: its own IP, held for 30 minutes
  const context = await browser.createBrowserContext({
    proxyServer: "http://gw.ludaxproxy.com:7777",
  });
  const token = randomBytes(4).toString("hex");
  const page = await context.newPage();
  await page.authenticate({
    username: "resi-lx_k3m9q2p7xa",
    password: `YOUR_PASSWORD-session-${token}-time-30-country-us`,
  });
  await page.goto("https://api.ludaxproxy.com/whoami");
  console.log(job, JSON.parse(await page.$eval("body", (body) => body.innerText)).ip);
  await context.close();
}
await browser.close();
```

### Geo-targeting with a matching language and time zone

```javascript
import puppeteer from "puppeteer";

// an IP in Munich, and a browser that looks local: German language, Berlin time
const browser = await puppeteer.launch({
  args: ["--proxy-server=http://gw.ludaxproxy.com:7777", "--lang=de-DE"],
});
const page = await browser.newPage();
await page.authenticate({
  username: "resi-lx_k3m9q2p7xa",
  password: "YOUR_PASSWORD-session-muc01-time-30-country-de-state-by-city-munich",
});
await page.emulateTimezone("Europe/Berlin");
await page.setExtraHTTPHeaders({ "Accept-Language": "de-DE,de;q=0.9" });

await page.goto("https://api.ludaxproxy.com/whoami");
console.log(await page.$eval("body", (body) => body.innerText));
console.log(await page.evaluate(() => [navigator.language, Intl.DateTimeFormat().resolvedOptions().timeZone]));
await browser.close();
```

### Block images, fonts and media to save bandwidth

```javascript
import puppeteer from "puppeteer";

const SKIP = new Set(["image", "font", "media"]);

const browser = await puppeteer.launch({
  args: ["--proxy-server=http://gw.ludaxproxy.com:7777"],
});
const page = await browser.newPage();
await page.authenticate({
  username: "resi-lx_k3m9q2p7xa",
  password: "YOUR_PASSWORD-session-wiki01-time-10-country-us",
});
// images, fonts and video never leave the browser, so they never count against your GB
await page.setRequestInterception(true);
page.on("request", (request) =>
  SKIP.has(request.resourceType()) ? request.abort() : request.continue(),
);

await page.goto("https://en.wikipedia.org/wiki/Proxy_server");
console.log(await page.title());
await browser.close();
```

### Retry a 502, stop on a refused login

```javascript
import puppeteer from "puppeteer";

// open a URL through Ludax: retry a dropped exit (502), stop at once on a refused login
async function open(page, url, tries = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      const response = await page.goto(url, { timeout: 30000 });
      // a refused login is not an exception: the page loads with status 407
      if (response?.status() === 407) throw new Error("407: check the username and password");
      return response;
    } catch (error) {
      const dropped = error.message.includes("ERR_TUNNEL_CONNECTION_FAILED");
      if (!dropped || attempt === tries) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1)));
    }
  }
}

const browser = await puppeteer.launch({
  args: ["--proxy-server=http://gw.ludaxproxy.com:7777"],
});
const page = await browser.newPage();
await page.authenticate({ username: "resi-lx_k3m9q2p7xa", password: "YOUR_PASSWORD-country-us" });
await open(page, "https://api.ludaxproxy.com/whoami");
console.log(await page.$eval("body", (body) => body.innerText));
await browser.close();
```

### puppeteer-extra with the stealth plugin

```bash
npm install puppeteer puppeteer-extra puppeteer-extra-plugin-stealth
```

```javascript
import puppeteer from "puppeteer-extra";
import StealthPlugin from "puppeteer-extra-plugin-stealth";

puppeteer.use(StealthPlugin());

// the proxy works the same way with puppeteer-extra: a flag, then authenticate()
const browser = await puppeteer.launch({
  args: ["--proxy-server=http://gw.ludaxproxy.com:7777"],
});
const page = await browser.newPage();
await page.authenticate({
  username: "resi-lx_k3m9q2p7xa",
  password: "YOUR_PASSWORD-session-st01-time-30-country-us",
});
await page.goto("https://api.ludaxproxy.com/whoami");
console.log(await page.$eval("body", (body) => body.innerText));
await browser.close();
```

## Plans and help

- Prepaid, no subscription: 1 GB of Residential Proxies costs $0.99. The smallest plan is the test; there is no free trial. Every plan: https://ludaxproxy.com/pricing
- Credentials: dashboard, Generate (https://ludaxproxy.com/dashboard). A plan's password can be reset there.
- Full reference: https://ludaxproxy.com/docs. Whole site for AI: https://ludaxproxy.com/llms.txt
- Support: Telegram https://t.me/LudaxProxy, email support@ludaxproxy.com.
