> ## Documentation Index
> Fetch the complete documentation index at: https://docs.roolink.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up your HTTP client

> The client that talks to the target site, configured once: Chrome's handshake, a sticky proxy, a cookie jar, and headers sent in the browser's order.

Roolink talks to Roolink. Everything that reaches the target site goes through a client you own, and the site reads that client's connection before it reads a single byte of payload. Python's `requests`, Go's `net/http` and Node's `fetch` each have a handshake of their own, and bot protection knows all three. This page builds a client whose handshake is Chrome's.

## Create the client

One constructor call sets everything the site fingerprints at connection time. Build it once per session and pass it around.

<CodeGroup>
  ```python Python theme={null}
  import os
  import tls_client

  def new_site_client() -> tls_client.Session:
      site = tls_client.Session(
          client_identifier="chrome_152",
          random_tls_extension_order=True,
          disable_http3=True,
      )
      site.proxies = {"http": os.environ["PROXY_URL"]}
      site.timeout = 30
      return site
  ```

  ```go Go theme={null}
  import (
  	"os"

  	tls_client "github.com/bogdanfinn/tls-client"
  	"github.com/bogdanfinn/tls-client/profiles"
  )

  func newSiteClient() (tls_client.HttpClient, error) {
  	return tls_client.NewHttpClient(tls_client.NewNoopLogger(),
  		tls_client.WithClientProfile(profiles.Chrome_152),
  		tls_client.WithRandomTLSExtensionOrder(),
  		tls_client.WithCookieJar(tls_client.NewCookieJar()),
  		tls_client.WithProxyUrl(os.Getenv("PROXY_URL")),
  		tls_client.WithDisableHttp3(),
  		tls_client.WithTimeoutSeconds(30),
  	)
  }
  ```

  ```typescript TypeScript theme={null}
  import { ModuleClient, SessionClient } from "tlsclientwrapper";

  const moduleClient = new ModuleClient();

  function newSiteClient() {
    return new SessionClient(moduleClient, {
      tlsClientIdentifier: "chrome_146",
      withRandomTLSExtensionOrder: true,
      disableHttp3: true,
      proxyUrl: process.env.PROXY_URL,
      timeoutSeconds: 30,
    });
  }
  ```
</CodeGroup>

The three libraries wrap the same engine, so a profile name means the same thing in every language and the rest of these docs treat them interchangeably.

The Python library installs from GitHub with `pip install git+https://github.com/Nintendocustom/Python-Tls-Client.git` and downloads the shared library it wraps on first use, so it keeps up with new Chrome profiles without a package release.

| Setting                | Why it is there                                                                                                                                                                                                                          |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Chrome profile         | Reproduces Chrome's TLS handshake: cipher suites, extensions, HTTP/2 settings. Use the newest Chrome profile the library offers. A gap of a few majors behind the User-Agent is tolerated; a profile from another browser family is not. |
| Random extension order | Chrome has shuffled its TLS extension order since version 110. A client that sends them in a fixed order stands out.                                                                                                                     |
| Proxy                  | Every request in a session leaves from one IP. See [Choose a proxy](#choose-a-proxy).                                                                                                                                                    |
| Cookie jar             | The site sets cookies on the first response and expects all of them back on the next request, including ones you never look at. Let the jar do it.                                                                                       |
| HTTP/3 off             | Most proxies cannot carry HTTP/3, and Chrome falls back to HTTP/2 through a proxy anyway. Turning it off keeps every connection on HTTP/2.                                                                                               |
| Timeout                | Sensor posts and challenge pages can be slow through residential proxies. Thirty seconds avoids false failures.                                                                                                                          |

## Send headers in the browser's order

Chrome sends headers in a fixed order that differs between a page load, a script load and a post from a script. Most HTTP libraries reorder headers silently; these clients let you pin the order, and you should on every request. The order comes from your [powhttp recording](/capture-requests), never from memory or from a guide.

<CodeGroup>
  ```python Python theme={null}
  PAGE_HEADERS = {
      # paste from your powhttp recording, top to bottom
  }

  def send(site, method, url, headers, data=None):
      site.header_order = list(headers)
      fn = site.get if method == "GET" else site.post
      return fn(url, headers=headers, data=data)
  ```

  ```go Go theme={null}
  import http "github.com/bogdanfinn/fhttp"

  var pageHeaders = [][2]string{
  	// paste from your powhttp recording, top to bottom
  }

  func send(site tls_client.HttpClient, method, url string, headers [][2]string, body io.Reader) (*http.Response, error) {
  	req, err := http.NewRequest(method, url, body)
  	if err != nil {
  		return nil, err
  	}
  	order := make([]string, 0, len(headers))
  	for _, h := range headers {
  		req.Header.Set(h[0], h[1])
  		order = append(order, h[0])
  	}
  	req.Header[http.HeaderOrderKey] = order
  	req.Header[http.PHeaderOrderKey] = []string{":method", ":authority", ":scheme", ":path"}
  	return site.Do(req)
  }
  ```

  ```typescript TypeScript theme={null}
  const PAGE_HEADERS: Record<string, string> = {
    // paste from your powhttp recording, top to bottom
  };

  async function send(site: InstanceType<typeof SessionClient>, method: "GET" | "POST", url: string, headers: Record<string, string>, body?: string) {
    const options = { headers, headerOrder: Object.keys(headers), body };
    return method === "GET" ? site.get(url, options) : site.post(url, options);
  }
  ```
</CodeGroup>

In Go, the pseudo-header order matters too. Chrome sends `:method`, `:authority`, `:scheme`, `:path`, and the line above pins it. Python and Node set that order for you.

## Choose a proxy

The site ties its cookies to the IP that earned them. Change IP mid-session and every cookie you hold is worthless.

* **Sticky, not rotating.** The proxy must keep one IP for as long as you ask. Start a new proxy session when you start a new browsing session, not partway through one.
* **Residential, not datacenter.** Addresses from consumer internet providers score well; datacenter ranges score badly regardless of what you send.
* **Region matches the site and the headers.** Pick a proxy in the site's home market and set `Accept-Language` to match. A US proxy with `en-US,en;q=0.9` is the usual pairing, and the `language` you send Roolink should name the same language; a tag or the full header value both work.

To confirm the proxy holds, request `https://api.ipify.org` through the client twice, a minute apart. Same IP both times, and not your own.

## One client per identity

A browser is one thing: one User-Agent, one handshake, one IP, one set of cookies. Keep that true in your code.

* Create a client per session and never share a cookie jar between sessions.
* Send the same User-Agent to the site and to Roolink, and keep the `sec-ch-ua*` headers in agreement with it. [Supported browsers](/supported-browsers) has the details.
* Release the client when the session ends. In Node call `destroySession()` on the session client and `terminate()` on the module client at shutdown; in Python call `site.close()`; in Go let the client go out of scope.

## Run it through powhttp

To compare your client with Chrome, point it at powhttp instead of your proxy for one run:

```bash theme={null}
export PROXY_URL="http://127.0.0.1:8080"   # the address shown in powhttp's window
```

powhttp records the handshake and the headers your client sent, next to Chrome's from the same site. [Capture requests with powhttp](/capture-requests) shows what to compare. Switch the proxy back before a real run; while your client goes through powhttp the site sees your own IP.
