node-tls-clientLatest version on npm

Custom fingerprints

Build a fingerprint from a JA3 string, HTTP/2 settings and TLS extensions.

When no built-in profile matches, describe the fingerprint yourself. Start from a capture of the client you want to copy, e.g. what tls.peet.ws reports for it, and fill in the matching options:

const session = new Session({
  // TLS ClientHello: version, ciphers, extensions, curves, point formats
  ja3string:
    "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
  supportedSignatureAlgorithms: [
    "ECDSAWithP256AndSHA256",
    "PSSWithSHA256",
    "PKCS1WithSHA256",
    "ECDSAWithP384AndSHA384",
    "PSSWithSHA384",
    "PKCS1WithSHA384",
    "PSSWithSHA512",
    "PKCS1WithSHA512",
  ],
  supportedVersions: ["GREASE", "1.3", "1.2"],
  keyShareCurves: ["GREASE", "X25519"],
  certCompressionAlgos: ["brotli"],
  alpnProtocols: ["h2", "http/1.1"],
  alpsProtocols: ["h2"],

  // HTTP/2 SETTINGS frame, window update and header frame priority
  h2Settings: {
    HEADER_TABLE_SIZE: 65536,
    ENABLE_PUSH: 0,
    INITIAL_WINDOW_SIZE: 6291456,
    MAX_HEADER_LIST_SIZE: 262144,
  },
  h2SettingsOrder: [
    "HEADER_TABLE_SIZE",
    "ENABLE_PUSH",
    "INITIAL_WINDOW_SIZE",
    "MAX_HEADER_LIST_SIZE",
  ],
  connectionFlow: 15663105,
  pseudoHeaderOrder: [":method", ":authority", ":scheme", ":path"],
  headerPriority: { streamDep: 0, exclusive: true, weight: 255 }, // weight: 0–255
});

const seen = await (await session.get("https://tls.peet.ws/api/all")).json();
console.log(seen.http2.akamai_fingerprint); // 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p

clientIdentifier wins

The custom options only apply when the session has a ja3string and no clientIdentifier. If both are set, clientIdentifier is used and all of the options below are silently ignored.

The pieces

Part of the fingerprintOptions
TLS ClientHelloja3string (TLS version, cipher suites, extensions, curves, point formats)
TLS extension contentssupportedSignatureAlgorithms, supportedVersions, keyShareCurves, certCompressionAlgos, alpnProtocols, alpsProtocols, supportedDelegatedCredentialsAlgorithms, recordSizeLimit
Extension orderrandomTlsExtensionOrder: true shuffles it per connection, as Chrome does
HTTP/2 connectionh2Settings + h2SettingsOrder (SETTINGS frame), connectionFlow (WINDOW_UPDATE), priorityFrames
HTTP/2 requestspseudoHeaderOrder, headerPriority
HTTP/3h3Settings, h3SettingsOrder, h3PseudoHeaderOrder, h3PriorityParam, h3SendGreaseFrames. See HTTP/3
Encrypted Client HelloechCandidatePayloads, echCandidateCipherSuites

Every option is listed with its type in SessionOptions; allowed string values are in Types.

Reading a capture

tls.peet.ws shows the values in the same shape:

  • tls.ja3 is the ja3string.
  • http2.akamai_fingerprint is SETTINGS|WINDOW_UPDATE|PRIORITY|pseudo-header order. For 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p:
    • 1:65536;2:0;… gives h2Settings and h2SettingsOrder. Ids: 1 HEADER_TABLE_SIZE, 2 ENABLE_PUSH, 3 MAX_CONCURRENT_STREAMS, 4 INITIAL_WINDOW_SIZE, 5 MAX_FRAME_SIZE, 6 MAX_HEADER_LIST_SIZE.
    • 15663105 gives connectionFlow.
    • 0 means no priorityFrames.
    • m,a,s,p gives pseudoHeaderOrder (:method, :authority, :scheme, :path).
  • The extensions list gives the signature algorithms, versions, key shares and ALPN/ALPS values.

The example above reproduces exactly that Akamai fingerprint.

Gotchas

  • GREASE. JA3 strings from Chrome contain GREASE values (e.g. 2570). List "GREASE" in supportedVersions and keyShareCurves where the browser sends it.
  • Weights are 0–255. A weight of 256 or more in headerPriority or priorityFrames fails every request (status: 0, "cannot unmarshal number 256").
  • headerPriority. When unset, a zeroed priority is sent, as in earlier versions. Pass null to send none.
  • ENABLE_PUSH. Use 0/1; booleans are converted.
  • certCompressionAlgos defaults to ["zlib"]. The singular certCompressionAlgo is deprecated.
  • Extension 51764 (trust anchors) requires trustAnchorsPayload, which the 1.16.0 native binaries ignore.

Check every change against a fingerprint echo service: a typo produces a valid-looking but different fingerprint.

On this page