Node.js binding for curl-impersonate via Koffi. A TypeScript port of curl_cffi.
impers is an HTTP client library for Node.js that can impersonate browsers' TLS/JA3 and HTTP/2 fingerprints. If you are blocked by some website for no obvious reason, you can give impers a try.
Node.js 18+ is required.
Warning
impers is in an alpha state. The docs may be inaccurate, some APIs may change without major version bump.
- Supports JA3/TLS and HTTP/2 fingerprint impersonation, including recent browsers and custom fingerprints.
- Fast performance powered by libcurl.
- Supports HTTP/2 and HTTP/3.
impersis probably the first Node package that support http/3. - Supports WebSocket, with impersonation.
- TypeScript first with full type definitions.
- MIT licensed.
| node-fetch | axios | got | undici | impers | |
|---|---|---|---|---|---|
| http/2 | ❌ | ❌ | ✅ | ✅ | ✅ |
| http/3 | ❌ | ❌ | ❌ | ❌ | ✅ |
| websocket | ❌ | ❌ | ❌ | ✅ | ✅ |
| fingerprints | ❌ | ❌ | ❌ | ❌ | ✅ |
impers is part of the impersonate suite.
- curl-impersonate. A curl distribution that impersonates browsers.
- curl_cffi. Python binding to curl-impersonate.
- impers. Node.js binding to curl-impersonate.
- impersonate.pro. Commercial support, more fingerprints and integrated solutions.
npm install imperslibcurl-impersonate: For full fingerprinting support, you need curl-impersonate installed. Standard libcurl works but without impersonation features.
If no local curl-impersonate library is found, impers downloads v2.3.2
(based on curl 8.23.0) at first launch. The download version is pinned and cached
by version so fresh installations use the same native library release.
To use your own version, set the LIBCURL_PATH environment variable:
export LIBCURL_PATH=/path/to/libcurl-impersonate.soSet IMPERS_LIBCURL_RELEASE_URL to a GitHub release API URL to override the pinned
download release.
import * as impers from "impers";
// Notice the impersonate parameter
const r = await impers.get("https://tls.peet.ws/api/all", { impersonate: "chrome" });
console.log(r.json());
// output: {..., "ja3_hash": "aa56c057ad164ec4fdcb7a5a283be9fc", ...}
// the JA3 fingerprint should be the same as the target browser
// To keep using the latest browser version as impers updates,
// simply set impersonate="chrome" without specifying a version.
// Other similar values are: "safari" and "firefox"
const r2 = await impers.get("https://tls.peet.ws/api/all", { impersonate: "chrome" });
// To pin a specific version, use version numbers together.
const r3 = await impers.get("https://tls.peet.ws/api/all", { impersonate: "chrome124" });
// To impersonate other than browsers, bring your own JA3/Akamai strings
// See examples directory for details.
const r4 = await impers.get("https://tls.peet.ws/api/all", {
ja3: "771,4865-4866-4867-49195-49199,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
akamai: "1:65536;3:1000;4:6291456;6:262144|15663105|0|m,a,s,p",
});
// HTTP/SOCKS proxies are supported
const r5 = await impers.get("https://tls.peet.ws/api/all", {
impersonate: "chrome",
proxy: "http://localhost:3128",
});
const r6 = await impers.get("https://tls.peet.ws/api/all", {
impersonate: "chrome",
proxy: "socks5://localhost:1080",
});import { Session } from "impers";
const session = new Session();
// httpbin is an HTTP test website, this endpoint makes the server set cookies
await session.get("https://httpbin.org/cookies/set/foo/bar");
console.log(session.cookies);
// Cookies { foo: 'bar' }
// retrieve cookies again to verify
const r = await session.get("https://httpbin.org/cookies");
console.log(r.json());
// { cookies: { foo: 'bar' } }
// Don't forget to close the session when done
await session.close();impers supports the same browser versions as curl-impersonate:
| Browser | Versions |
|---|---|
| Chrome | chrome99, chrome100, chrome101, chrome104, chrome107, chrome110, chrome116, chrome119, chrome120, chrome123, chrome124, chrome131, chrome133a, chrome136, chrome142, chrome145, chrome146, chrome150, chrome151, chrome151_macos, chrome152, chrome152_macos |
| Chrome Android | chrome99_android, chrome131_android |
| Safari | safari153, safari155, safari170, safari180, safari184, safari260, safari2601, safari270 |
| Safari iOS | safari172_ios, safari180_ios, safari184_ios, safari260_ios, safari270_ios |
| Firefox | firefox133, firefox135, firefox144, firefox147, firefox153, firefox154, firefox155 |
| Tor | tor145 |
| Edge | edge99, edge101 |
The aliases chrome, firefox, safari, and safari_ios select chrome152,
firefox155, safari270, and safari270_ios. Chrome 151/152 and Firefox 153–155
use Windows profiles; use the explicit _macos Chrome targets for macOS.
Dedicated HTTP/3 fingerprints are available for Chrome 145, 146, 150, 151, and
152 (including the macOS variants), Firefox 147, and Safari 27.0. Select
httpVersion: "v3" to allow fallback, or "v3only" to require HTTP/3.
Firefox 153–155 have TLS and HTTP/2 fingerprints only. Native profiles select
their default headers for the negotiated protocol; request headers override
those defaults.
If you are trying to impersonate a target other than a browser, use ja3 and akamai options to specify your own customized fingerprints.
import * as impers from "impers";
// JA3 TLS fingerprint format: tls_version,ciphers,extensions,curves,curve_formats
const ja3 = "771,4865-4866-4867-49195-49199,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0";
// Akamai HTTP/2 fingerprint format: settings|window_update|streams|header_order
const akamai = "1:65536;3:1000;4:6291456;6:262144|15663105|0|m,a,s,p";
const r = await impers.get("https://tls.peet.ws/api/all", { ja3, akamai });
// For fine-grained control, use extraFp
const extraFp: impers.ExtraFingerprint = {
tlsSigAlgs: ["ecdsa_secp256r1_sha256", "rsa_pss_rsae_sha256"],
tlsSupportedGroups: ["X25519", "P-256", "P-384"],
http2Settings: {
1: 65536, // HEADER_TABLE_SIZE
3: 1000, // MAX_CONCURRENT_STREAMS
4: 6291456, // INITIAL_WINDOW_SIZE
6: 262144, // MAX_HEADER_LIST_SIZE
},
http2PseudoHeaderOrder: ["m", "a", "s", "p"],
};
const r2 = await impers.get("https://tls.peet.ws/api/all", { extraFp });The impers command only manages fingerprint API access and the local cache:
npx impers config --api-key YOUR_API_KEY
npx impers update
npx impers list
npx impers list --jsonUse a cached target name directly, or edit a copied Fingerprint before sending it:
const fingerprint = impers.getFingerprint("chrome_152_macos_26.0");
fingerprint.headers["User-Agent"] = "custom user agent";
const response = await impers.get("https://example.com", {
impersonate: fingerprint,
});import { Session } from "impers";
const urls = [
"https://httpbin.org/get",
"https://httpbin.org/ip",
"https://httpbin.org/user-agent",
];
const session = new Session({ impersonate: "chrome124" });
try {
const results = await Promise.all(
urls.map(url => session.get(url))
);
for (const r of results) {
console.log(r.status, r.url);
}
} finally {
await session.close();
}import * as impers from "impers";
const ws = await impers.wsConnect("wss://echo.websocket.org", {
impersonate: "chrome124",
});
await ws.sendStr("Hello, World!");
for await (const message of ws) {
console.log("Received:", message);
if (message.type === "text" && message.data.toString() === "Hello, World!") {
break;
}
}
await ws.close();Use perMessageDeflate: true in wsConnect options to negotiate compression
with curl-impersonate 2.3.0. If the server declines, messages remain uncompressed.
import { Curl, CurlOpt } from "impers";
const curl = new Curl();
const chunks: Buffer[] = [];
try {
curl.setOpt(CurlOpt.URL, "https://example.com");
curl.setWriteFunction((chunk) => chunks.push(Buffer.from(chunk)));
// Browser impersonation (requires curl-impersonate)
curl.impersonate("chrome124");
// Or use manual fingerprinting
// curl.setJa3("771,4865-4866-...");
// curl.setAkamai("1:65536;...");
curl.perform();
console.log(Buffer.concat(chunks).toString());
} finally {
curl.cleanup();
}import * as impers from "impers";
// Generic request
const r = await impers.request("POST", url, options);
// Convenience methods
const r1 = await impers.get(url, options);
const r2 = await impers.post(url, { json: { key: "value" } });
const r3 = await impers.put(url, { data: { key: "value" } });
const r4 = await impers.del(url);import { Session } from "impers";
const session = new Session({
baseUrl: "https://api.example.com",
impersonate: "chrome124",
headers: { "X-Custom-Header": "value" },
timeout: 30,
proxy: "http://localhost:3128",
});
const r = await session.get("/endpoint");
await session.close();| Option | Type | Description |
|---|---|---|
params |
object |
URL query parameters |
headers |
object |
Request headers |
cookies |
object |
Request cookies |
data |
object | string |
Form data (application/x-www-form-urlencoded) |
json |
any |
JSON body (automatically sets Content-Type) |
multipart |
MultipartField[] |
Multipart form fields and file parts |
files |
object |
Files for multipart upload |
content |
string | Buffer |
Raw body content |
readCallback |
(size: number) => Buffer | string | null | undefined |
Callback for streamed request body chunks |
readCallbackSize |
number | bigint |
Total byte size for readCallback request bodies |
auth |
object |
HTTP authentication (basic, digest, bearer) |
proxy |
string |
Proxy URL |
timeout |
number |
Request timeout in seconds |
connectTimeout |
number |
Connection timeout in seconds |
allowRedirects |
boolean |
Follow redirects (default: true) |
maxRedirects |
number |
Maximum redirects (default: 30) |
verify |
boolean |
Verify SSL certificates (default: true) |
impersonate |
string | Fingerprint |
Builtin/cached target or editable fingerprint |
ja3 |
string |
JA3 TLS fingerprint string |
akamai |
string |
Akamai HTTP/2 fingerprint string |
extraFp |
ExtraFingerprint |
Fine-grained fingerprint options |
stream |
boolean |
Do not buffer response content |
contentCallback |
(chunk: Buffer) => void |
Callback for response body chunks; honors decodeContent |
decodeContent |
boolean |
Decode buffered and callback response bodies using libcurl (default: true); false returns compressed bytes |
headerCallback |
(chunk: Buffer) => void |
Callback for raw response header chunks |
httpVersion |
string |
Force HTTP version ("1.0", "1.1", "2", "3") |
Decoding support depends on the compression libraries compiled into libcurl. Response headers retain the server's original Content-Encoding and Content-Length, even when the body is decoded.
import * as impers from "impers";
const r = await impers.get(url);
r.status; // HTTP status code
r.statusText; // HTTP status text
r.headers; // Response headers
r.cookies; // Response cookies
r.url; // Final URL (after redirects)
r.text; // Response body as string
r.content; // Response body as Buffer
r.json(); // Parse response as JSON
r.elapsed; // Request duration in seconds- TypeScript port inspired by curl_cffi.
- FFI bindings powered by Koffi.
- Browser impersonation powered by curl-impersonate.
MIT