Self-hosted service
carcabot/tiktok-signature avatar
carcabot/tiktok-signature

tiktok-signature: generating X-Bogus and X-Gnarly signatures with a headless browser

Generate tiktok signature token using node

1,046 stars340 forksJavaScriptLicense varies

At a glance

What is it?
tiktok-signature is a Node service that signs TikTok web API URLs with X-Bogus and X-Gnarly parameters by driving TikTok's own SDK inside a headless Chromium session. It works, but the signature is bound to the browser fingerprint it was produced in, so the whole design turns on keeping the request and the browser environment consistent.
Who is it for?
Adopt tiktok-signature if you are building a Node service that must call TikTok web endpoints and you want signing, session refresh and fingerprint consistency handled by a local service you can inspect. Do not adopt it if you need a one-off script, if you are not prepared to keep Chromium and a browser automation stack in your deployment, or if your target endpoints change their signing scheme.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the signature is for, and why a browser is involved

TikTok's web endpoints expect two extra query parameters, X-Bogus and X-Gnarly, alongside the ordinary parameters of a request. tiktok-signature is a service whose whole job is to produce those two values.

The mechanism the project chose is not a reimplementation of the signing algorithm. The README states that the service uses a headless browser with TikTok's own SDK to generate authentic signatures that work reliably, and that the SDK is injected locally for reliability. In other words, the signature is computed by running TikTok's JavaScript in a real browser environment rather than by translating the algorithm into Node.

That choice explains nearly every other property of the project. If the signing code runs inside a page, then the browser's own state matters: the cookies, the user agent, the screen size, the platform string, the language. A signature is therefore not a free-standing token you can attach to any request. It is a function of an environment, and it only works if the request that carries it comes from the same environment.

The dependency list backs this up. package.json pulls in puppeteer, puppeteer-extra, puppeteer-extra-plugin-stealth and undici, with Node 18 or newer required. The stealth plugin is there to make the automated browser look ordinary, and undici provides the HTTP client. The primary language of the repository is JavaScript, and the main entry point is server.mjs, an ES module started with node and an optional .env file.

The project positions itself as free and open source in a market where signature generation is usually sold as a service, and it invites sponsorship through a buymeacoffee link.

Two endpoints, and the difference between them matters

The service exposes two ways to get data out of TikTok, and the README is clear that they are not interchangeable.

POST /signature is the recommended one. You send it a URL, it returns a signed_url carrying the X-Bogus and X-Gnarly parameters, plus the pieces of browser fingerprint your request has to match: a user_agent, a device-id, cookies, and a navigator object with platform, browser_language, os, screen_width and screen_height. Your own application then makes the actual HTTP request to TikTok. The stated advantage is control: you handle rate limiting, retries and parallel requests yourself, which is exactly what you want when the signature is cheap to obtain but the request volume is not.

POST /fetch is the fallback. Here the request goes through the browser session itself, and the response comes back already fetched. The README describes this as bypassing TikTok's bot detection entirely, but it also says plainly: use it only as a fallback when external requests with signed URLs fail, because it is slower and less scalable since each request goes through the browser. The response carries httpStatus and contentType, and for a non-JSON target such as a video page, data is null and the raw HTML comes back in text so you can extract embedded page data yourself.

Two smaller endpoints round out the surface. GET /health reports status, ready, initMethod, sessionAgeMinutes, generationCount, maxGenerationsBeforeRefresh, queueLength and proxyEnabled, which is the endpoint to watch in production. GET /restart forces a browser session restart, described as useful if signatures stop working, which tells you the authors expect that failure mode to occur.

Running it locally, and the Docker path

The quick start is a short sequence of shell commands. Clone the repository, install the npm dependencies, install Chromium for Puppeteer, copy the environment file, and start the server:

bash
git clone https://github.com/carcabot/tiktok-signature.git
cd tiktok-signature
npm install
npx puppeteer browsers install chromium
cp .env.example .env
npm start

The start script is node --env-file-if-exists=.env server.mjs, so the .env file is optional at the Node level, though the quick start copies it anyway. The env file is where the operational knobs live: PORT defaults to 8080, the proxy block takes PROXY_ENABLED, PROXY_HOST, PROXY_USER and PROXY_PASS, PUPPETEER_EXECUTABLE_PATH is commented out and auto-detected by default, and two commented entries tune the auto-refresh, MAX_GENERATIONS_BEFORE_REFRESH at 500 and MAX_SESSION_AGE_MS at 1800000.

The Docker path is the one to use for a service. The compose file builds from the Dockerfile, maps 8080:8080, passes the same environment variables with defaults, restarts unless-stopped, and defines a healthcheck that curls http://localhost:8080/health every 30 seconds with a 60 second start period. Resource limits are set at two CPUs and 2G of memory, with a reservation of half a CPU and 512M.

The Dockerfile is worth a look because it differs from the local install in one significant way. It starts from node:20-slim, installs the system Chromium package with its shared library dependencies, then sets PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium and PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true so the image does not download a second copy of the browser. The runtime is therefore the distribution's Chromium, not the one Puppeteer would fetch, and that difference is the kind of detail that changes a browser fingerprint.

Session refresh, the queue, and proxy rotation

Because the browser session is the source of truth for the signature, its lifetime is the central operational concern. The README lists auto-refresh of the browser session every 500 signatures or every 30 minutes, and both numbers are configurable through the environment variables above.

The health endpoint exposes the state directly: sessionAgeMinutes tells you how old the current session is, generationCount how many signatures have come out of it, maxGenerationsBeforeRefresh the 500 threshold, and queueLength how many requests are waiting. This is a well-instrumented service for a program whose failure mode is otherwise invisible. A signing service that quietly starts producing invalid signatures is worse than one that crashes, because the caller only finds out when TikTok starts returning errors or empty results.

The queue system handles concurrent requests safely. The README lists it among the features without giving the queue depth or the concurrency limit, so the practical capacity of a single instance is something to determine by measurement rather than from the documentation.

Proxy configuration is there for IP rotation, with PROXY_ENABLED as the switch and the host, user and password fields alongside it. Note that the proxy is applied at the browser level, since the browser is what makes the request in the /fetch path and what holds the session in the /signature path.

The User-Agent coupling, which is where integrations break

The README puts one instruction in bold and it deserves the emphasis. When making requests to TikTok you must use the user_agent from the response, because the signature is tied to the browser environment and a mismatched User-Agent will cause failures.

That single constraint shapes the entire client design, and the Node example in the README shows it in practice. The example builds the API URL with a full set of fingerprint parameters, WebIdLastTime, aid, app_language, app_name, browser_language, browser_name, browser_online, browser_platform, browser_version, channel, cookie_enabled, count, cursor, device_platform, focus_state, history_len, is_fullscreen, is_page_visible, language, os, priority_region, region, screen_height, screen_width, secUid, tz_name and webcast_language, then posts that URL to the /signature endpoint, then makes its own request to TikTok with headers taken from the response:

javascript
const response = await fetch(data.signed_url, {
  headers: {
    "User-Agent": data.navigator.user_agent,
    Cookie: data.cookies,
    Accept: "application/json",
    Referer: "https://www.tiktok.com/",
  },
});

The user agent, the cookies and the URL parameters all come from the same signing session. Mixing a signature from one session with the cookies or parameters from another is the failure the warning is about, and it produces a rejected request rather than an error message that points at the cause.

The navigator values in the response example are a macOS Safari string with platform MacIntel, browser_language en-US, screen 1920 by 1080, which tells you the default session fingerprint. The README describes the navigator object as the set of fingerprint values you should use when building API URLs, so that the URL and the signature agree.

A local browser is the price of admission

The limitations here are structural, and they are worth stating plainly because they are not the kind a feature request can fix.

First, the footprint. This is not a library you import and call. It is a service that holds a Chromium process alive, and the compose file reserves memory for it. The Dockerfile installs the system packages for the browser and its shared libraries. A deployment that cannot run a container, or that has a tight memory budget, cannot run this at all. The alternative shape, a library that returns a signature, does not exist in this project.

Second, the coupling to TikTok's current behaviour. The service injects TikTok's own SDK, which means it tracks whatever TikTok deploys. When the signing scheme changes or the SDK's assumptions change, the fix is a code change, not a configuration change. The presence of a GET /restart endpoint that exists for the case where signatures stop working is a reminder that the service has a natural failure mode.

Third, the browser-based /fetch path is documented as slower and less scalable, and it is the path that avoids relying on your own request matching the browser environment. The trade-off is real: /signature is fast but imposes a consistency contract on your client, and /fetch is forgiving but slow.

Fourth, the README does not say anything about TikTok's terms of service, and that silence is worth noting rather than glossing over. Whether a given use of signed requests is permitted is a question for the platform's terms, not something this repository answers.

The repository layout is otherwise sensible for a project of this kind. The examples/ directory holds a working client for each endpoint type, including basic-signature.js, user-videos.js, user-info.js, comments.js, search.js, trending.js, hashtag.js, music.js, fetch-videos.js and a shell variant fetch-videos.sh, and there is a __tests__/ directory with jest configured through the test scripts. The benchmark.mjs file at the root is the performance testing tool, and the xgnarly.mjs file is copied into the image alongside the server and the javascript/ directory, though the README does not describe what it does.

Where the service sits against a client-side implementation

The alternative approach is to compute the signature in your own process instead of in a browser, which is the way the algorithm was originally circulated. The difference in approach is where the computation happens and who owns the environment.

A client-side implementation puts the signing code in your Node process. There is no Chromium, no container, no session to refresh and no fingerprint to keep in sync, because the parameters you send are the ones you computed. The costs are that you are maintaining a copy of TikTok's signing logic yourself, which means following their changes by hand, and that you are on the hook for the moment it breaks.

The browser approach inverts that. You do not reimplement anything, so you are not the one who updates when TikTok changes, and the generated values are authentic because they came from the real SDK. In exchange you carry a browser, a session lifecycle, an auto-refresh schedule and a consistency contract between two places, the signer and the caller.

There is a middle path visible in the repository itself. The root contains xgnarly.mjs next to server.mjs, and the javascript/ directory is copied into the image separately, and the Dockerfile exists to run the browser-based variant. The README does not document the relationship between these pieces, so the boundary between the local signing path and the browser path is not something the documentation settles. That is the first file to open if you want to know how much of the browser machinery the signature actually requires.

For most callers, the practical decision is simpler. If you are writing a service that makes many requests and need to control rate limiting and parallelism, the browser-based signature service with the /signature endpoint is the shape that fits. If you are writing a script that makes a handful of requests and cannot justify a container, the browser is a heavy dependency for the job.

Editorial conclusion

Adopt tiktok-signature if you are building a Node service that must call TikTok web endpoints and you want signing, session refresh and fingerprint consistency handled by a local service you can inspect. Do not adopt it if you need a one-off script, if you are not prepared to keep Chromium and a browser automation stack in your deployment, or if your target endpoints change their signing scheme. Verify first that the response navigator user_agent is used verbatim on the request, that the /health endpoint reports ready before you send traffic, and that the version you deploy matches the TikTok signing scheme it was built against, since the last release listed is v4.3.6 from 2026-05-31 and the package.json version field reads 4.3.7.

Frequently asked questions

How does tiktok-signature generate the X-Bogus and X-Gnarly parameters?

The README states that the service uses a headless browser with TikTok's own SDK, injected locally for reliability, to generate authentic signatures. The project depends on puppeteer, puppeteer-extra, puppeteer-extra-plugin-stealth and undici, and requires Node 18 or newer.

What is the difference between the /signature and /fetch endpoints?

POST /signature returns a signed URL plus the browser fingerprint, and your application makes the actual request to TikTok, which lets you control rate limiting, retries and parallelism. POST /fetch makes the request through the browser session itself, which the README describes as slower and less scalable and recommends only as a fallback.

Why do I have to use the user_agent from the response?

The README warns that the signature is tied to the browser environment, so a mismatched User-Agent causes failures. The navigator object in the /signature response carries the user_agent, platform, browser_language, os, screen_width and screen_height that your request has to match.

How do I run tiktok-signature with Docker?

The compose file builds from the Dockerfile, maps 8080:8080 and passes PORT, PROXY_ENABLED, PROXY_HOST, PROXY_USER, PROXY_PASS, MAX_GENERATIONS_BEFORE_REFRESH and MAX_SESSION_AGE_MS, with a healthcheck against http://localhost:8080/health. The image is based on node:20-slim and uses the system Chromium at /usr/bin/chromium.

What happens when signatures stop working?

The README documents a GET /restart endpoint for restarting the browser session, described as useful if signatures stop working. The session is also refreshed automatically every 500 signatures or every 30 minutes, and both limits are configurable through MAX_GENERATIONS_BEFORE_REFRESH and MAX_SESSION_AGE_MS.

Official sources

  1. carcabot/tiktok-signature on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/carcabot-tiktok-signature.svg)](https://hysenlabs.com/projects/carcabot-tiktok-signature)

Was this useful?