We Found a Pre-Released WhatsApp Feature

While researching WhatsApp web for a project that we’ll write about soon, we accidentally found that WhatsApp has a feature flag for a hidden feature – one that allows users to change the playback rate of voice messages.

Broken Behavior

One of our research tools allows us to change the playback rate of voice messages. Every voice message is essentially an audio tag with a blob of an audio source.

document.querySelectorAll('audio').forEach(e => {
  e.playbackRate = 2;
});

This snippet would change the speed of all WhatsApp voice messages to 2x speed.

Running it today did nothing. No errors in the console or wired side effects, the messages were playing at a normal speed.

Reverse Engineering

We’re pretty fluid in reverse engineering web and mobile applications, and we’d already looked into the WhatsApp web minified source code, so we had a good hunch on where to look.

It didn’t take long to find these lines of code:

...
initialize() {
  i.default.prototype.initialize.call(this),
    this.playbackRate = (0,
      s.getPttPlaybackRate)(),
    this.listenTo(this, "change:playbackRate", ((e,t)=>{
        (0,
          s.setPttPlaybackRate)(t)
      }
    ))
}
...

These lines add a listener for change:playbackRate, which is triggered every time an audio tag’s playback rate changes. It then calls setPttPlaybackRate, which overrides the playback rate to a pre-computed value. This was disabling our own tool from setting the playback rate.

Why would WhatsApp do that? And what value were they overriding with?

setPttPlaybackRate uses an Enum to determine which playback speed value to set:

...
t.pttPlaybackSpeed = function(e) {
  switch (e) {
    case 1:
      return v.default.PTT_PLAYBACK_SPEED_TYPE.SPEED_1;
    case 1.5:
      return v.default.PTT_PLAYBACK_SPEED_TYPE.SPEED_1_5;
    case 2:
      return v.default.PTT_PLAYBACK_SPEED_TYPE.SPEED_2;
    default:
      throw new Error("Invalid playback rate: ".concat(e))
  }
}(this.props.pttPrefs.playbackRate),
...

So WhatsApp have three pre-determined speeds to choose from. This raised our suspicions that something else was going on here – a new feature perhaps? We set to find out what was going on.

Some background context first. A few parts of WhatsApp web, and specifically those that make audio playback, use a “simple” React app. It uses a not-so-far-from-standard Webpack build.

Working through the components that make up the audio tags, we found this:

...
if (m.default.pttPlaybackSpeedEnabled && "ptt" === i) {
  P = b === r.toString();
  var A = (0,
    s.default)(o.default.playbackRateButtonContainer, {
    [o.default.playbackRateButtonContainerVisible]: P
  });

...

Using Chrome DevTools, we set a breakpoint on that line of code and inspected the value of m.default.pttPlaybackSpeedEnabled:

Dynamically looking at the value of the feature flag

Interesting! What would happen if we set this flag to true? We quickly jumped to the console, while the the execution was still stopped by the breakpoint, and ran:

m.default.pttPlaybackSpeedEnabled = true

And magically, all our voice messages had a new playback rate indicator. In a dark theme, it looks like this:

The end result