Find the bundle and identify its format
React Native keeps its JavaScript in the assets folder of the APK, usually as index.android.bundle. An app shipped as an Android App Bundle can place the same file under a per-architecture or per-feature asset directory, so list the archive contents rather than assuming the default path.
The first bytes decide how you read it.
- A readable file is JavaScript. Metro bundled and minified it, so names are short but the code still parses as source.
- A binary file is a Hermes bytecode module. It carries a header with a format version, a function table and a string table.
- A compressed or encrypted file was transformed after bundling. The decryption step happens at runtime, and the plaintext can be read from memory as the runtime loads it.
Endpoints in the bundle
In plain JavaScript, read the bundle as source. Endpoint paths, header names, storage keys and JSON field names survive minification because the runtime looks them up by name on objects and in storage, and those literals cannot be renamed safely. A search for a scheme, a leading slash or a known header name produces the endpoint inventory quickly.
In Hermes bytecode, the string table is the fastest route and can be read without understanding a single instruction. The table gathers literals in one place, and endpoint strings sit next to the modules that use them. A disassembler such as hermes-dec or hbctool exposes the header and the table, and following a string to the functions that reference it shows the call site.
React Native obfuscation usually focuses on the JavaScript rather than on the Java layer, which works in your favour: after the bundle is readable, the endpoint map comes back fast even when local variable names are gone.
Base URLs that never appear as one string
A common obstacle is that no literal contains a full URL. React Native apps build the base address at runtime from configuration, from a native constant read over a platform channel, or from a host selected by a build flag. The bundle then holds path fragments and a configuration object instead of one address.
Two steps resolve this. First, search the bundle for a path fragment you recognise from captured traffic and read the object it is joined to. Second, inspect the Java or Kotlin layer for constants exposed to the runtime, because a value that comes from native code will not be in the JavaScript at all. Captured traffic confirms which combination of host and path the running app actually uses.
Headers and request signing in JavaScript
Header construction is usually visible in the bundle next to the call. An interceptor, a wrapper around fetch or a generated client sets the authentication header, the content type and any application identifier.
When the app signs requests, the signing code is in the bundle as well, which makes the algorithm easier to read than a compiled implementation. Constants, key material and the exact order in which values are concatenated appear as source. The obstacle in that case is not obfuscation but confirmation: the same code path may run through a native module for part of its work, so the recovered algorithm still has to be checked against what the app actually sends.
Values produced by native modules cannot be recovered from the bundle at all. An attestation result, a keystore-backed signature or an identifier derived from build properties exists only on the device, and those values sit on the boundary between the JavaScript and the platform.
What the bundle does not tell you
The bundle is a static map. It shows the endpoints, the payload shapes and the names of the values, but not which values are generated fresh on each call, how a session is established, or what the app stores between launches so that it can skip a step later.
That last point matters for a client that has to run unattended. Token storage keys visible in the bundle show which state is cached, and the flow that refreshes it is visible in source, but reproducing the sequence reliably still requires watching a real session. Signing confirmation, identifier generation and token refresh are the three areas where the static reading has to be checked against observed behaviour.
From bundle to a working client
The bundle answers what to call and what to send, and runtime observation answers what has to be true when the call is made. Combining them produces a client that reproduces the requests rather than replaying a capture. SReverse recovers APIs from React Native apps and delivers a Python, JavaScript or TypeScript client with a Postman collection and documentation. Projects start at $120 and most are delivered in 24 to 72 hours. Apps that mix React Native screens with a heavy native layer are covered under APK to callable API.
Related work
Reviewed 28 September 2026 · SReverse research desk