Investigation of an Unofficial Wink API

I was able to bypass Wink’s SSL pinning and was able to successfully intercept the traffic between the Wink app on my iPhone 6s and their API. This page will be dedicated to my findings and I will be giving cURL commands that can be run, and then explain what sort of data is being returned. This post will be sorted in chronological order in what requests the app sends. When trying to change the state of a lightbulb it first tries to connect with the hub locally and then it forwards the request on to their API.

  1. Step 1. Logging into Wink:
  • cURL: curl -H 'Host: api.wink.com' -H 'Accept: */*' --data-binary '{"client_id":"xxxxxx","username":"email address","password":"password","client_secret":"CODE","grant_type":"password"}' --compressed 'https://api.wink.com/oauth2/token'

  • What has been removed: device identifier(this appears unnecessary entirely as requests are still processed even if i don’t even include that field), client id, username/password, along with my client secret.

  • Returned values:data”: { “access_token”: “”, “refresh_token”: “”, “token_type”: “bearer”, “token_endpoint”: “https://api.wink.com/oauth2/token”, “scopes”: “full_access”, “object_type”: “access_token”, “object_id”: “” }, “errors”: [], “pagination”: {}, “access_token”: “”, “refresh_token”: “”, “token_type”: “bearer”, “token_endpoint”: “https://api.wink.com/oauth2/token”, “scopes”: “full_access”, “object_type”: “access_token”, “object_id”: “” }

  1. Whenever you load the app after logging in previously
  • cURL: curl -H 'Host: api.wink.com' -H 'X-CLIENT-VERSION: IOS-10.2' -H 'Accept: */*' -H 'Date: Sat, 18 Feb 2017 11:54:27 MST' -H 'X-API-VERSION: 1.0' -H 'X-DEVICE-IDENTIFIER: ' -H 'User-Agent: Manufacturer/Apple-iPhone8_1 iOS/10.2 WinkiOS/5.5.0.20-production-release (Scale/2.00)' -H 'Accept-Language: en;q=1, el;q=0.9' -H 'Authorization: Bearer [insert access token]' --compressed 'https://api.wink.com/users/me'

  • Data returned”data”: { “user_id”: “”, “first_name”: “”, “last_name”: “”, “email”: “@gmail.com”, “locale”: “en_us”, “units”: { “temperature”: “f” }, “tos_accepted”: true, “confirmed”: true, “last_reading”: { “units”: { “temperature”: “f” }, “units_updated_at”: 1429078344.6488109, “current_location”: null, “current_location_updated_at”: null, “home_geofence_id”: null, “home_geofence_id_updated_at”: null, “robot_subscription”: null, “robot_subscription_updated_at”: null, “current_location_data”: null, “current_location_data_updated_at”: null, “general_tos_version”: “8”, “general_tos_version_updated_at”: 1485036365.7719002, “premium_tos_version”: null, “premium_tos_version_updated_at”: null, “feature_flags”: [“faster_lights”, “ask_for_feedback”], “feature_flags_updated_at”: 1464109667.884032, “desired_units”: { “temperature”: “f” }, “desired_units_updated_at”: 1429078342.416597, “desired_current_location”: null, “desired_current_location_updated_at”: null, “desired_home_geofence_id”: null, “desired_home_geofence_id_updated_at”: null, “desired_robot_subscription”: null, “desired_robot_subscription_updated_at”: null }, “created_at”: 1408771226, “object_type”: “user”, “object_id”: “”, “uuid”: “”, “desired_state”: { “units”: { “temperature”: “f” } }, “subscription”: { “pubnub”: { “subscribe_key”: “sub-c-f7”, “channel”: “4f” } } }, “errors”: [], “pagination”: {}, “subscription”: { “pubnub”: { “subscribe_key”: “sub-c-f7”, “channel”: “4f” } } }

  1. It then tries to see what connected products you have integrated into the Wink app.
  • cURL: curl -H 'Host: api.wink.com' ' -H 'Accept: */*' -H 'Authorization: Bearer ' --compressed 'https://api.wink.com/users/me/linked_services'

  • The pubbnub suscribe key never seems to change but the channel key is unique per device, and changed depending on the device, such as LightSwitch1 will have a different channel than LighSwitch2,etc.

  • Not going to list outputs as it will vary person to person depending on what API’s they have integrated into their Wink account.

  1. It pulls data for all the devices connected to your account.
  • cURL: curl -H 'Host: api.wink.com' -H 'Accept: */*' -H ' -H 'Authorization: Bearer ' --compressed 'https://api.wink.com/users/me/wink_devices'

  • This then returns the varying different field for all devices connected, the ones that seem to always be listed are : “Object Type”, “Object ID”, The connection status of the device, along with the pub nub channel.

  • Among the most important of these is the data it returns about the hub:{ “object_type”: “hub”, “object_id”: “”, “uuid”: “”, “icon_id”: null, “icon_code”: null, “desired_state”: {}, “last_reading”: { “connection”: true, “connection_updated_at”: 1487384817.3780499, “agent_session_id”: “”, “agent_session_id_updated_at”: 1487384803.5743198, “pairing_mode”: “idle”, “pairing_mode_updated_at”: 1487384817.3780499, “pairing_device_type_selector”: null, “pairing_device_type_selector_updated_at”: null, “kidde_radio_code”: 0, “kidde_radio_code_updated_at”: 1455410733.7809336, “pairing_mode_duration”: 0, “pairing_mode_duration_updated_at”: 1487384817.3780499, “led_brightness”: null, “led_brightness_updated_at”: 1487384817.3780499, “updating_firmware”: false, “updating_firmware_updated_at”: 1487384801.7396967, “firmware_version”: “3.5.23-0-g3fc556ce74-hub2-app”, “firmware_version_updated_at”: 1487384805.4154928, “update_needed”: false, “update_needed_updated_at”: 1487384805.4154928, “mac_address”: “D8:”, “mac_address_updated_at”: 1487384805.4154928, “zigbee_mac_address”: “00000000”, “zigbee_mac_address_updated_at”: 1487384817.3780499, “ip_address”: “192.168.1.29”, “ip_address_updated_at”: 1487384805.4154928, “hub_version”: “00.01”, “hub_version_updated_at”: 1487384805.4154928, “app_version”: “3.5.23-0-g3fc556ce74-hub2-app”, “app_version_updated_at”: 1487384805.4154928, “transfer_mode”: null, “transfer_mode_updated_at”: 1487384803.5743198, “connection_type”: “ethernet”, “connection_type_updated_at”: 1487384817.3780499, “wifi_credentials_present”: true, “wifi_credentials_present_updated_at”: 1487384817.3780499, “remote_pairable”: true, “remote_pairable_updated_at”: 1470346311.279153, “local_control_public_key_hash”: “A3:99:C1:3D”, “local_control_public_key_hash_updated_at”: 1487384816.9427545, “local_control_id”: “3e”, “local_control_id_updated_at”: 1487384816.9427545, “desired_pairing_mode_updated_at”: 1485733610.06795, “desired_pairing_device_type_selector_updated_at”: 1485733593.307652, “desired_kidde_radio_code_updated_at”: 1455410734.0713968, “desired_pairing_mode_duration_updated_at”: 1485733610.06795, “desired_led_brightness”: null, “desired_led_brightness_updated_at”: null, “updating_firmware_changed_at”: 1487384801.7396967, “connection_changed_at”: 1487384801.7396967, “agent_session_id_changed_at”: 1487384803.5743198, “firmware_version_changed_at”: 1487384804.1493583, “app_version_changed_at”: 1487384804.1493583, “update_needed_changed_at”: 1487384804.1493583 }, “subscription”: { “pubnub”: { “subscribe_key”: “sub-c-f7, “channel”: “3d|hub-|user-“ } }, “hub_id”: “364150”, “name”: “Wink Hub”, “locale”: “en_us”, “units”: {}, “created_at”: 1455410732, “hidden_at”: null, “capabilities”: { “oauth2_clients”: [“wink_hub_2”], “home_security_device”: true, “provisioning_version”: “1”, “needs_wifi_network_list”: true }, “user_ids”: [””, “”, “”, “”], “triggers”: [], “manufacturer_device_model”: “wink_hub2”, “manufacturer_device_id”: null, “device_manufacturer”: “wink”, “model_name”: “Hub2”, “upc_id”: “821”, “upc_code”: “wink_hub_2”, “linked_service_id”: null, “lat_lng”: [], “location”: “”, “update_needed”: false },
    1. A request is sent to find all the different groups you have, most importantly it lists the local ids for the devices
  • cURL: curl -H 'Host: api.wink.com' -H 'Accept: */*' -H 'Authorization: Bearer ' --compressed 'https://api.wink.com/users/me/groups'

  • Example of returned data: object_type”: “binary_switch”, “object_id”: “”, “local_id”: “14”, “hub_id”: “”, “blacklisted_readings”: [] }

5.Let’s skip to sending a command to the hub locally. Unfortunately whenever I try and run the cUrl command I am returned with a an SSL error saying something is wrong with my certificate so if you want to connect with the hub you most run the cURL command with option -k. Also the bearer token it sends is different than the one used to communicate with the Wink API. It also seems to connect at some point locally to the Wink Relay. Also I haven’t found the reason behind the nonce, if you discover there reason I would appreciate if you would let me know.

  • cURL without including insecure option: curl -H 'Host: 192.168.1.29:8888' -H 'Authorization: Bearer ' --data-binary '{"desired_state":{"powered":false,"brightness":1},"nonce":1230276045}' -X PUT --compressed 'https://192.168.1.29:8888/light_bulbs/8'

5.1. It also queries the hub for all devices unfortunately it appears the name it has tied to the local ids, is the name/brand of the switch. Not the name you assign to it in the Wink app.

*cUrl: curl -H 'Host: 192.168.1.29:8888' -H 'Authorization: Bearer ' -H 'Accept: */*' -H 'Accept-Language: en;q=1' -H 'User-Agent: Wink/5.4 (iPad; iOS 8.2; Scale/2.00)' --compressed 'https://192.168.1.29:8888/devices'

5.2 How to find your local Key. cURL: curl -H 'Host: api.wink.com' -H 'Accept: */*' --data-binary '{"local_control_id":"[This can be found when you poll api.wink.com/users/me/wink_devices]","scope":"local_control","grant_type":"refresh_token","refresh_token":"[Use the refresh you generated when you first logged in step 1] ", "client_secret":"xxxxxx","client_id":"xxxxx"}' --compressed 'https://api.wink.com/oauth2/token'

*This then retuned another access key which can be used to connect to the hub locally. The refresh token returned doesn’t change from the one included in the request. It also included a scopes string but I have yet to figure out the purpose of it.

  • Also if you are trying to send a command to the Cloud and Locally the way it makes sure the command isn’t repeated is that it sends the “locally_activated_objects”:”object_type”: “light_bulb”,”object_id”: “” to the cloud api as well as it must include the same nonce as the local. This seems to be how it makes sure the command isn’t run twice. If you send one without that or with a different nonce then it gets freaky.Also if you seems to only be getting API 1 responses to cloud commands with an API 2 key I found that you must include the User-Agent, otherwise it seems unnecessary.
Written on March 4, 2017