reverse-engineering

I Reverse-Engineered Uber to See How It Prices Your Ride

October 25, 2026·21 min read·Shelly Fourer

TL;DR: I decompiled the Uber rider app (v4.653.10004). Before Uber shows you a price, it builds a file on you. Who you are (including your rating and trip history), where you're going, how you pay, which pricing experiments you've been put in, and a 56-field fingerprint of your phone (battery, VPN, GPS, even how you tap). What it sends back isn't one number. It's a range (minFare, maxFare, pointEstimateFare), and you only see one point inside it. Your fare even comes back tagged with an experiment group. And your price and your driver's pay are worked out by two separate algorithms. I could see all of that in the code. The one thing I couldn't see is the actual formula that picks your number. That runs on Uber's servers, and I'm not going to pretend otherwise.

How does Uber decide your price? (the short answer)

When you open Uber and ask for a fare, the app sends its servers a request built from four objects of data about you: your rider profile (rating, trip history, payment), your location and destination, the pricing experiments you've been sorted into, and a 56-field fingerprint of your phone. Uber's servers reply with a fare range, a minimum, a maximum, and one chosen point. You only ever see the point.

So does Uber charge different people different prices? Yes, by destination: a peer-reviewed study found riders going to pricier hotels paid more for effectively the same trip. Does it charge you more for a low battery or other personal signals? That's plausible but unproven, because the formula that picks your number runs on Uber's servers, not in the app. The rest of this post shows exactly what I found, and exactly where the proof stops.

Why I did this

Uber is worth around $200 billion. Last year riders paid it about $193 billion in gross bookings, and it kept roughly $52 billion in revenue. (Uber FY2025 results)

On a $100 ride, your driver takes home around $70, and Uber keeps the rest (before tips). I wanted to know how a company actually builds the number you see, and whether anything in the app explains why the same route never seems to cost the same twice.

So I pulled apart the actual app.

How I did it (methodology)

Everything here comes from lawful static analysis of the shipped app. No bypassing login, no touching Uber's servers, no private data.

  1. Got the APK. The Uber rider app (v4.653.10004, stable) from APKMirror. The same binary any Android user installs.
  2. Decompiled it with jadx 1.5.6. The app is about 205 MB of code across 23 DEX files, roughly 173,000 Java classes once decompiled.
  3. Read the pricing module (com.ubercab.presidio.pricing.core) and the request/response models, then searched strings across all the bytecode.
  4. Cross-checked the web app with network capture, and the financials against Uber's SEC filings and an academic study.

Every class and field name below is real and quoted from the decompiled source.

Finding 1: Before you see a price, Uber assembles a dossier about you

When you ask Uber for a price, the app sends a request to its pricing service (SurgeRequest, plus the fare request RidersFareEstimateRequest). That request is not just "where am I, where am I going." It carries who you are, how you pay, which experiments you're in, and a 56-field fingerprint of your phone. Here's the actual payload, grouped. Every field name is quoted straight from the decompiled source, with the class it comes from:

WHO YOU ARE
  rating                            ← your rider rating        (Rider)
  displayRating                                                (Rider)
  totalCompletedRidersTripsCount    ← how many trips you've taken (Rider)
  riderUuid                         ← your account             (SurgeRequest)
  profileUUID / profileType         ← personal or business     (RidersFareEstimateRequest)
  email, mobileDigits                                          (Rider)
 
WHERE YOU'RE GOING
  deviceLocation                    ← where you are now        (SurgeRequest)
  pinLocation / destinationLocation ← pickup + drop-off        (SurgeRequest)
  viaLocations                      ← stops along the way      (RidersFareEstimateRequest)
  cityId                                                       (SurgeRequest)
 
HOW YOU PAY
  paymentProfileUUID                ← which payment method     (SurgeRequest)
  lastSelectedPaymentProfileUUID    ← your last-used card      (Rider)
  uberCashToggleOn, voucherUUID                                (RidersFareEstimateRequest)
 
WHICH EXPERIMENTS YOU'RE IN
  userExperiments                   ← your experiment buckets  (RidersFareEstimateRequest)
  experimentalFeaturesSpecification ← fare experiments         (RidersFareEstimateRequest)
 
YOUR DEVICE FINGERPRINT
  deviceData { ...56 fields... }    ← battery, VPN, GPS, taps  (DeviceData)

So "your phone sends 56 things" actually undersells it. The 56 is just the device fingerprint. Your rating, your trip count, your payment method, and your experiment group ride along in the same request. (Your rating lives in a separate Rider object, not in DeviceData, but it's in the file all the same.)

So what's the real total? There isn't one honest number. The request is nested, objects inside objects, some of them lists, so any "Uber collects exactly N things" claim is made-up precision. What I can give you is the exact field count of each object on the price path, straight from the source. Count them yourself:

ObjectFieldsWhat it is
SurgeRequest31the pricing/surge request
RidersFareEstimateRequest48the fare-estimate request
DeviceData56the device fingerprint (nested inside the request)
Rider37your rider profile: rating, trip count, payment, credits, referrals

That's four objects, 170+ top-level fields, before you even expand the nested ones (every Location adds lat/lng/accuracy/bearing, impressions is a list, userExperiments is a map). So the honest line isn't "Uber collects 172 things about you." It's "the price request is built from objects totaling well over a hundred fields, and the device fingerprint alone is 56." Here's that full 56.

The device fingerprint is the part people doubt most, so I'm not going to say "56+ fields" and ask you to trust me. Here is every single field on that DeviceData class, straight from the decompiled source. All 56 of them:

// com.uber.model.core.generated.rtapi.models.deviceData.DeviceData
public class DeviceData {
    // --- Who / which device ---
    private final String  androidId;          // Android device ID
    private final String  device;
    private final String  deviceModel;
    private final String  deviceName;
    private final String  deviceOs;
    private final String  deviceOsName;
    private final String  deviceOsVersion;
    private final String  serialNumber;        // hardware serial
    private final DeviceIds deviceIds;          // advertising / vendor IDs
    private final String  cpuAbi;
    private final String  cpuType;
    private final Integer libCount;             // installed native libs
    private final String  language;
    private final String  systemTimeZone;
    private final Boolean hasNFC;
 
    // --- Network / carrier / connection ---
    private final String  carrier;
    private final String  carrierMcc;           // mobile country code
    private final String  carrierMnc;           // mobile network code
    private final String  connectedVpn;         // are you on a VPN
    private final String  ipAddress;
    private final Boolean wifiConnected;
    private final String  imsi;                 // SIM subscriber ID
    private final String  simSerial;            // SIM serial number
    private final String  phoneNumber;
 
    // --- Location / motion ---
    private final Double  deviceLatitude;
    private final Double  deviceLongitude;
    private final Double  deviceAltitude;
    private final Double  course;               // heading
    private final Double  speed;
    private final Double  horizontalAccuracy;
    private final Double  verticalAccuracy;
    private final Boolean locationServiceEnabled;
 
    // --- Battery / power ---
    private final Double  batteryLevel;          // your battery level
    private final String  batteryStatus;         // charging? plugged in?
 
    // --- Fraud / tamper signals (the "is this a real phone" checks) ---
    private final Boolean rooted;                // is your phone rooted
    private final Boolean emulator;              // are you an emulator
    private final Boolean mockGpsOn;             // are you faking GPS
    private final Boolean unknownSources;        // sideloaded-apps setting
    private final String  deviceCheckToken;      // Apple/Google attestation
    private final DeviceClassification deviceClassification;
    private final AppPermissionsData appPermissionData; // which permissions you granted
 
    // --- Behavior ---
    private final UserInteractionData userInteractionData; // how you tap / interact
 
    // --- Browser / web context ---
    private final String  browserName;
    private final String  browserVersion;
    private final String  userAgent;
 
    // --- App / build / integrity ---
    private final String  version;
    private final String  versionChecksum;
    private final String  specVersion;
    private final String  envId;
    private final String  envChecksum;
    private final String  md5;                   // integrity hash
    private final String  source;
    private final String  sourceApp;
    private final String  data;
    private final String  session;
    private final TimestampInMs epoch;
    private final ImmutableList<ContactlessPaymentSupport> contactlessPaymentSupport;
}

(The grouping and inline comments are mine, for readability. The field names and types are exactly as they appear in the app. md5 shows up as f37826md5 in the raw decompile because of a name collision, but it's the md5 field.)

Here's the honest part. This DeviceData is built by Uber's fraud module (com.ubercab.fraud). It's there to catch fake accounts, stolen cards, and bots, which is normal for any app that moves money. A viral video recently claimed Uber "scans your phone to overcharge you." The fingerprinting is real, but it lives in a package literally named fraud, and nothing in the app shows it feeding the price. More on that later.

Finding 2: Your fare isn't a number. It's a range.

This is the one that surprised me. The object Uber's server sends back for a fare estimate looks like this:

// com.uber.model.core.generated.rtapi.models.auditablecontent.AuditableFareEstimateRange
public class AuditableFareEstimateRange {
    private final double minFare;            // the lowest your fare could be
    private final double maxFare;            // the highest your fare could be
    private final Double pointEstimateFare;  // the ONE number they show you
}

Your fare has a minimum, a maximum, and a point estimate. That point is the single number that actually shows up on your screen. You never see the range. You just see the point. This model is referenced across 11 classes in the app (fareRangeMin, fareRangeMax, pointEstimateString, updatedFareRange), so it's not dead code. It's how fares are represented.

What I can say: the price you see was picked from a band Uber's server already worked out. What I can't say: how it picks the point inside that band. That math is server-side.

Finding 3: Fares run through an A/B-testing system

Your fare request carries an ExperimentalFeaturesSpecification, and the bytecode is full of fare-specific experiment keys:

fares_exp_point_fare_estimate
order_selection_m2_fares_treatment
u4b_xp_profiles_fare_binding
u4b_xp_fares_cache_invalidation
DEMAND_SHAPING_FARE_EXPLAINER

xp is Uber's prefix for experiments. In plain terms: your fare runs through an experiment system, the same way a store A/B tests a button color.

And it goes further than just how the price looks. The response to your price request (RidersFareEstimateResponse) hands the app back your experiment group, attached to your fare:

// RidersFareEstimateResponse
private final Map<String, FaresExperimentData> userExperiments;
 
// FaresExperimentData
public class FaresExperimentData {
    private final String treatmentGroup;              // which arm of the experiment you got
    private final Map<String, String> additionalData;
}
 
// ExperimentationMetadata (the server ranks several fare versions and picks one)
public class ExperimentationMetadata {
    private final FareContent styledPrimaryFare;              // the one you see
    private final ImmutableList<FareContent> rankedSecondaryFare; // the others it considered
}

I want to be careful here, because this is exactly the spot where honest teardowns and clickbait split apart:

  • Observed: Uber sorts you into fare groups and sends them back with your price. There's even an experiment named u4b_xp_profiles_fare_binding that ties your profile to your fare. The server works out several fare versions and picks one.
  • Inference (reasonable): Since fares are tied to groups, the group you land in can change what you pay. That's what A/B testing a price means.
  • Unknown: Whether you're put in a group at random (normal A/B) or on purpose, using your file (rating, history, device). That logic runs on Uber's servers. It's not in the app.

And here's the thing that makes the end of the trail provable instead of a shrug. I checked, and the app never calculates a fare. Nowhere in Uber's pricing code (com.ubercab.presidio.pricing.core) is there a formula, a multiplier, or any fare = f(your data) logic. The FareBinder classes do exactly one thing: take the server's number and put it on a TextView (void a(TextView)). The app reads minFare, maxFare, and pointEstimateFare, then shows you the point. The math was kept off the phone on purpose. That's not an accident. It's the reason nobody, me included, can pull "Uber's pricing formula" out of a decompile.

Finding 4: Your price and your driver's pay are two separate algorithms

Since Uber's 2023 pricing change, what you pay and what your driver earns are worked out separately. A 2025 academic study tracked 114 London drivers before and after and found:

  • Uber's median take rate rose from 25% to 29%, and some trips went over 50%.
  • Uber's surplus per driver-hour rose 38%.
  • On higher-value fares, drivers earned less per minute.

So you can pay more on the exact same trip your driver earns less on, and Uber keeps the gap. On some rides, Uber now takes home more than the driver does.

Finding 5: Your quoted price is signed and expires

// BackwardCompatibleUpfrontFareSignatureData
public class UpfrontFareSignatureData {
    private final String signature;   // cryptographic signature of your fare
    private final Double issuedAt;    // when the price was issued
    private final Double expiresAt;   // when the price expires
}

Once your number is chosen, Uber signs it and stamps an expiry on it. Your price is locked to you for a few minutes, then it refreshes. That's not a glitch, it's how it's built. It's also why your estimate changes if you sit on the screen too long.

Finding 6: Uber also built a "wait and save" nudge

The flip side of surge. Uber's app has a demand-shaping feature:

// com.uber.model.core.generated.rtapi.services.marketplacerider.DemandShapingStatus
public class DemandShapingStatus {
    private final int    timeToRequestSec;   // how long to wait
    private final String savingsValue;       // how much you'd save by waiting
    private final String counterFooter;      // the countdown
}

Surge says "pay more now." Demand shaping says "wait N seconds, save $X." Both are Uber steering its marketplace to balance its own supply and demand, using your price as the lever.

What I could NOT prove (and why that matters)

This is the part most "Uber exposed" videos skip, and skipping it is exactly how they get things wrong.

  • The fare formula is server-side. The app gets a range and a point. It doesn't work them out. I can't show you the equation that picks your number, because it isn't in the app.
  • Battery and device data changing your price is unproven. Yes, your battery level is in the DeviceData sent with ride requests. But that object is built by the fraud module, and no code in the app connects it to pricing. "It's collected" is true. "It sets your price" is not something I can show.
  • A/B testing how the price looks is not the same as proven price discrimination. The experiment system is real, and your fare comes back tagged with a treatmentGroup (see Finding 3). But whether you're put in that group at random, or on purpose based on your file, isn't visible from the app.
  • That targeting is the real unknown. I can see that you're sorted into fare experiments, and that groups carry fares. I can't see what decides your group. If it's your rating, your history, or your device, that call happens on Uber's servers.

If someone tells you they "proved Uber charges you more because of your phone," ask them to show the server code. They can't. Nobody outside Uber can. And I confirmed why: the app has no fare math in it at all (Finding 3).

So how would I actually close this gap? Not by guessing. By catching a real fare response as it happens. You'd run the app instrumented, request a ride, and read the actual treatmentGroup and minFare/maxFare/pointEstimateFare the server sends back. That turns "the field exists" into "here's my real group and my real hidden range." It's a bigger job, and Uber's own fraud checks (those rooted and emulator fields above) are built to make it hard. So that's the planned v2 of this teardown, not something I'm claiming today.

But here's what is proven: Uber price-discriminates by destination

I can't prove the server charges you more because of your battery. But economists have proven something close, with real data and real peer review, and it's worth knowing the difference.

In "Does Uber Benefit Travelers by Price Discrimination?" (Chang, Winston & Yan, Journal of Law and Economics), researchers (one from the Brookings Institution) pulled real UberX fares for trips from three airports (LAX, JFK, SFO) to hotels. They tested whether Uber charges more when you're heading somewhere that suggests you'll pay more.

The clever part is how they ruled out "it's just a longer trip." They matched up pairs of hotels within 0.1 miles of each other, same stretch of road, same distance, same duration, and compared the fares. They also controlled for distance, trip duration, and time of day with fixed effects.

The result: fares are clearly and significantly tied to the destination hotel's room rate. A rider going to a pricier-than-average hotel paid, on average, $1.03 more in LA, $0.85 in NY, $0.63 in SF, on what was basically the same trip. In their words, Uber "exploits market segmentation with considerable precision."

That's third-degree price discrimination: pricing by where you're going (a stand-in for how much you'll pay), proven on real trips. What no study has proven is the personal kind, where Uber charges you more because of your battery, phone, or history. The destination effect is documented. The "it reads your phone and overcharges you" claim is not.

This is the honest version of the story, and it's the stronger one. Uber clearly prices by context, and a $200B pricing operation almost certainly isn't ignoring those 56 personal signals. But "almost certainly" isn't proof, and I'm not going to dress it up as one.

So what is Uber, really?

Based on what's actually in the app, Uber isn't just a ride company. It's a pricing machine that happens to move you from A to B. It reads 56 signals off your phone, builds your fare as a hidden range, tests how the number looks, signs it and gives it an expiry, and pays your driver on a separate formula. $52 billion a year, one quietly chosen number at a time.

For builders: the patterns worth stealing

If you're building anything that charges money, these are real, legitimate patterns worth understanding (and the reason this teardown is more than just outrage bait):

  • Range pricing. Model a price as a band, not a single point, and choose where to land inside it.
  • Price experimentation. A/B test what converts, carefully and legally.
  • Splitting the two sides of a marketplace. What the buyer pays and what the supplier earns don't have to come from the same formula.
  • Signed, expiring quotes. Lock a price to a session so it can't be tampered with or go stale.

Frequently asked questions about Uber pricing

Isn't a price range normal? Every estimate has a min and max.

Showing you an estimate range is normal. What's different here is that the app gets a minFare, a maxFare, and a separate pointEstimateFare, a single chosen point, and that point is what you're shown and charged against. You see the point, not the band it came from. That's the part worth knowing.

Does my battery level actually change my Uber price?

I can't show that it does, and I won't claim it. batteryLevel is a real field in the DeviceData sent with ride requests. But that object is built by Uber's fraud module (com.ubercab.fraud), and no code in the app connects it to the fare. "It's collected" is true. "It sets your price" is not something the app proves.

Do the 56 things Uber collects actually change my price?

Not that anyone can prove. Here's the honest split.

Proven: Uber charges different fares based on your destination. A peer-reviewed study (Chang, Winston & Yan in the Journal of Law and Economics) found riders heading to pricier hotels paid up to about $1 more on basically the same trip.

Not proven: that any of the 56 device signals (battery, VPN, phone model, how you tap) feed your price. The famous "low battery means higher price" claim comes from a 2016 interview where Uber's own research head said a low battery is a strong predictor of willingness to pay. But Uber says it doesn't use it, and no serious study has shown it does. One small newspaper test found a 6% difference, which is an anecdote, not proof. Even the FTC's 2025 "surveillance pricing" study found personal-data pricing across industries, but didn't tie a specific device signal to a specific Uber fare.

So: destination-based pricing is real and documented. Personal-signal pricing is plausible, but unproven.

Why can't you just show the formula that picks my number?

Because it isn't in the app. The app sends your request and gets back a range with a chosen point. The logic that works out those numbers runs on Uber's servers, which I didn't access and can't see. Anyone showing you "Uber's pricing formula" from a decompile is showing you how the app handles the result, not the formula.

Is Uber pricing the same on iPhone as on Android?

This teardown is the Android app (v4.653.10004). The iOS app is a separate binary I didn't look at here. It's the same account hitting the same servers either way, but I only claim what I actually verified on Android.

Does using a VPN change my Uber price?

connectedVpn is one of the fields sent in DeviceData, so Uber knows whether you're on a VPN. Does it affect price? Same answer as battery: not shown in the app, not something I'll claim.

Is reverse-engineering the Uber app legal?

Nothing here shows illegal behavior. Dynamic pricing, device-based fraud detection, and A/B testing are all legal and common. This teardown used lawful static analysis of the publicly shipped app, with no bypassing of login and no access to Uber's servers. The point isn't "gotcha, it's illegal." It's "here's how the machine is actually built."

Why does Uber run pricing on its servers instead of in the app?

Control and secrecy. Server-side pricing can be changed instantly for everyone, can't be inspected by people like me, and keeps the real model private. The app only ever gets the final result.

How I know this is real

The app. Uber rider v4.653.10004, the stable release, pulled as an .apkm bundle from APKMirror. The same binary any Android user installs.

  • Bundle SHA-256: b2c3ba8d991fe867f8bc8e675f2146f4bf2b87927c4d95174ed6d202ea23587b
  • base.apk SHA-256: de844ba8bbf781c068862bb95a05224ce59bd017466087617599388b0cb08d19
  • Size: 196 MB of code across 23 DEX files.

The decompile. jadx 1.5.6 produced about 173,000 Java classes. Every field and class name in this post is from that output, not paraphrased.

The findings are load-bearing, not stray strings. The fare-range model (AuditableFareEstimateRange, pointEstimateFare, fareRangeMin/Max) shows up across 11 classes, so it's how fares are represented, not dead code. The device data object is wired into the SurgeRequest the app builds. And the experiment keys (fares_exp_point_fare_estimate, u4b_xp_profiles_fare_binding, ExperimentalFeaturesSpecification) sit right on the fare request path.

You can reproduce it. Download the same version from APKMirror, verify the hash, and decompile:

# verify you have the same binary
shasum -a 256 base.apk
# -> de844ba8bbf781c068862bb95a05224ce59bd017466087617599388b0cb08d19
 
# decompile
jadx --no-res base.apk -d uber-out
 
# find the fare-range model yourself
grep -rl "AuditableFareEstimateRange\|pointEstimateFare" uber-out/sources

Where the trail ends. The fare value itself is worked out on Uber's servers. I didn't touch those servers, and I can't show you the formula, because it isn't in the app. Every claim in this post is about what the app sends and how it handles the result. That line, between what I verified and what stays server-side, is the whole point.

Sources

Decompiled code is used here for analysis, commentary, and interoperability research. All identifiers are quoted from the shipped app for the purpose of explaining its behavior.

// newsletter

Get new posts in your inbox

No spam. Unsubscribe any time.