Wyze Local API - Encoding Changes

Hello,

For the last couple of months I’ve been using a “local API” discovered for Wyze color bulbs. I was able to make a simple HTTP POST request to http://bulbip:80/device_request with the following JSON

{"request":"set_status","isSendQueue":0,"characteristics":[{"pid":"P1507","pvalue":"0000ff"}]}

With an update in the last 1-2 weeks, the characteristics field for this “local API” appears to now be encoded, and the bulbs no longer respond to local requests with the plain JSON.

Example captured from an iOS device (using Burp):

{"request":"set_status","isSendQueue":0,"characteristics": "dq0PeZ4yUSCh9nDPvLDdEqm8bpDa7AoA7lHaeQqSSqxN2NfQf0b07TM0YRQGz1jPaKez\/NtExWiEyXkv7fsLLDe5rP\/QaVd+HLpKkhdjf8UM4QKcD5Olwmy0NMRqjlHVYKu0pcnwXolPEj29R+Np7ZJe7HjXazkkuXg9uE0HGpg="}

This new update seems to do the following:

  1. Local bulb requests are changed from port 80 to port 88
  2. Local bulb requests now have an encoded characteristics field.

The issue is trying to figure out what sort of encoding is being used to change what used to be a simple pid and pvalue to a new encoded string.

I’ve tried decoding it as base64 among other things, but to no luck. Additionally, it appears to be using a timestamp perhaps as a hash/salt? Even firing off the same requests from the app produces differing local requests every time.

Examples of local API traffic from the app → bulb
Bulb Off #1

{"isSendQueue":"0","request":"set_status","characteristics":"PeqR2WzXYqvl9ncW5WJyhEgTi1XOP9vGsezVU1yK4K9c4tkCPMswNzdCvOSITv1q1b\/Y\/CBzJ3cprbvEPWuS0\/n3VloMfltK7QNQjejT7uA5d\/I3OjNZVjv5qSf2mWp5"}

Bulb On #1

{"isSendQueue":"0","request":"set_status","characteristics":"PeqR2WzXYqvl9ncW5WJyhEpf4D+53awQCS+BHZrsh\/EDQJrD6DfHt6JWzwUrpFwClCdiziwRN\/DXzTWKMgegoxp8TcI\/fZ5\/HVjUnd5ybKvlXUTuifYkLfXBq3vhHgiv"}

Bulb Off #2

{"isSendQueue":"0","request":"set_status","characteristics":"PeqR2WzXYqvl9ncW5WJyhClwJWs0BjD5SmNN\/N3AGvtDv53zT3b9+XXf9IHEovCi3QlAGrraMuLjrtZIWQ\/mclFvfg4v5KimLi7EfH7zBadFufI4dxQK3+8fFFtmwxfG"}

Bulb On #2

{"isSendQueue":"0","request":"set_status","characteristics":"Gwoyljs4txZqQRsjG3650hrapOfggdB9sHvQiaAEYEhY\/2zkPnhW\/JWapxHvYvgvurUrR4dPFDO52urRKjrLEB1NYKdxa1m2flS\/UYOWP3HUp8R2OiogvDGh+tscQu8k"}

Bulb Off #3

{"isSendQueue":"0","request":"set_status","characteristics":"Gwoyljs4txZqQRsjG3650slXPbIEYF07EB4bGt5jpcO4HW5NZ0hvkJsucXztkzFHoWkSo8XqRdDYX1ASdx+pi9yIsXyvGjXrHVyZanD0WHjich7eT6GQldaky1Qdtx+p"}

Bulb On #3

{"isSendQueue":"0","request":"set_status","characteristics":"Gwoyljs4txZqQRsjG3650jlaMhsws\/odczQ9sU5x+rY2AxIXb\/Te\/MHB4Qner7FFrmwzWH9yenNc6GhNcLuY4dG\/nrOUzp9PfEzbwfvsoopPfqd+sr31h1F9YVUMVWkx"}

I know there is no official API, but it would be amazing to still use this “local API” for our own applications. This way we can send requests entirely locally, without any external rate limiting or strain on Wyze. Does anyone have any pointers on trying to figure out what sort of new encoding is being used on this characteristics field?

Sounds like you’re far ahead of a lot of us, and thanks for the info. I didn’t even know any of the devices were accepting local queries like that. Other than a possible friendly “leak” from a Wyze staffer I don’t know if you’ll get any helpful responses. But I hope you do!

For anyone wondering:

I found this article where Josh decompiled Wyze’s Android app for some more information on how Wyze was encrypting their traffic.

I ended up hiring Josh for a full implementation (of the local network API) with Node JS specifically. However, I can share a bit more of the current process details…

Here’s an example of a local POST:
http://bulbip:88/device_request

{
  "request": "set_status",
  "isSendQueue": 0,
  // The entire characteristics property will be encrypted before it is sent
  "characteristics": {
    "mac": "12345ABCD", // Bulb MAC address
    "index": "1",
    "ts": 1234567890, // Timestamp
    "plist": [
        {
          "pid": "P3", // Power State
          "pvalue": "1" // On/Off
        },
        {
          "pid": "P1507", // Color State
          "pvalue": "0000ff" // HEX Color
        },
        {
          "pid": "P1501", // Brightness 1-100
          "pvalue": "100" // Brightness
        },
    ]
  }
}

Every Wyze bulb now has an enr property. This is used as the “key” and “IV” for AES encryption. Existing libraries for both Node JS and Python can already retrieve this property for the bulb(s).

The characteristics field for the local API is then encrypted with AES CBC, with both the key and IV as the bulb’s enr value.

I assume the characteristics field suddenly being encrypted is for the sake of security. Even though you have to still be on the same (ideally secured) network, my guess is Wyze added this for an additional layer?

For my implementation, it’s amazing to have local network control over these bulbs to change color, power, etc. multiple times a second if needed - without any sort of rate-limiting with external servers.

This is fantastic info and thanks again.

Also, you resisted mentioning how useful this would be in achieving continued local control during a sustained AWS or other Internet outage such as everyone just experienced, but I won’t. :wink: