Skip to content

Instantly share code, notes, and snippets.

@StephenBlackWasAlreadyTaken
Last active April 25, 2024 13:38
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save StephenBlackWasAlreadyTaken/adb0525344bedade1e25 to your computer and use it in GitHub Desktop.
Save StephenBlackWasAlreadyTaken/adb0525344bedade1e25 to your computer and use it in GitHub Desktop.
DexcomShare Endpoints for the Uploader App
  • these are the calls used by the dexcom uploader app
  • these are in no particular order!
  • User-Agent: Dexcom%20Share/3.0.2.11 CFNetwork/672.0.2 Darwin/14.0.0

General

Read Dexcoms System time clock

GET

/ShareWebServices/Services/General/SystemUtcTime

Body:

  none

Response:

  {
    "DateTime":"\/Date(1426767421178)\/",
    "OffsetMinutes":0
  }

Session Related

Login to a Publisher Account (Get a Session ID):

POST

/ShareWebServices/Services/General/LoginPublisherAccountByName

Body:

  {
    "accountName":"yourlogin",
    "applicationId":"d8665ade-9673-4e27-9ff6-92db4ce13d13",
    "password":"yourpassword"
  }

Response:

  "e3e3e6a5-coolsessionid-bro”

Authenticate Publisher Account (Get a Session ID):

POST

/ShareWebServices/Services/General/AuthenticatePublisherAccount

Body:

  {
    "accountName":"yourlogin",
    "applicationId":"d8665ade-9673-4e27-9ff6-92db4ce13d13",
    "password":"yourpassword"
  }

Response:

  "e3e3e6a5-coolsessionid-bro”

Check if the Reciever is assigned to your account

POST

/ShareWebServices/Services/Publisher/CheckMonitoredReceiverAssignmentStatus?sessionId={YourSessionId}&serialNumber={YourSerialNumber}/

Body:

  none

Response:

  `AssignedToYou` or `NotAssigned` (plaintext)

Assign the reciever to you (If you got NotAssigned or something else)

POST

/ShareWebServices/Services/Publisher/ReplacePublisherAccountMonitoredReceiver?sessionId={YourSessionId}&serialNumber={YourSerialNumber}

Body:

  none

Response:

  No Idea, Someone please tell me, if you assign one to yourself that was
  already yours you get a 500 error

Check remote monitoring session is valid

POST

/ShareWebServices/Services/Publisher/IsRemoteMonitoringSessionActive?sessionId={YourSessionId}

Body:

  none

Response:

  `true` or `false` (plaintext)

Start remote monitoring session

POST

/ShareWebServices/Services/Publisher/StartRemoteMonitoringSession?sessionId={yoursessionid}&serialNumber={YourdexcomSerialNumber}

Body:

  none

Response:

  just a status, `200`

Stop a remote monitoring session

POST

/ShareWebServices/Services/Publisher/StopRemoteMonitoringSession?sessionId={YourSessionId}

Body:

  none

Response:

  just a status, `200`

Update Publisher information (might be fun for sending them cute messages?)

POST

/ShareWebServices/Services/Publisher/UpdatePublisherAccountRuntimeInfo

Body:

  {
    "sessionId":"YourSessionId",
    "runtimeInfo":
      {
        "DeviceManufacturer":"Apple",
        "DeviceModel":"iPhone5,2",
        "DeviceOsVersion":"7.0.2",
        "AppVersion":"3.0.2.11",
        "AppName":"DexcomShare",
        "AppNumber":"SW10569",
        "DeviceOsName":"iPhone OS"
      }
  }

Response:

  just a status, `200`

Data!

Post BG Data

POST

/ShareWebServices/Services/Publisher/PostReceiverEgvRecords?sessionId={yourSessionId}

Body:

{
  "SN":"YourSerialNumber",
  "Egvs":[
      {
        "Trend":4,
        "ST":"\/Date(1426783106000)\/",
        "DT":"\/Date(1426754317000)\/",
        "Value":97
      }
    ],
  "TA":-14365
}

Response:

  just a status, `200`

ST system time, DT display time, TA is a time offset, multiply by 1000 and subtract it from the time (so subtracting a negative in this example, which is really adding)

Read BG Data

POST

/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues?sessionId={YourSessionId}&minutes=1440&maxCount=1

Body:

  none

Response:

[
  {
    "DT":"\/Date(1426780716000-0700)\/",
    "ST":"\/Date(1426784306000)\/",
    "Trend":4,
    "Value":99,
    "WT":"\/Date(1426769941000)\/"
  }
]

Invite Follower Related

Check if someone is already a contact of yours

POST

/ShareWebServices/Services/Publisher/DoesContactExistByName?sessionId={YourSessionId}&contactName={NameOfNewFollower}

Body:

  none

Response:

  `true` or `false` (plaintext)

Create a contact if they dont already exist

POST

/ShareWebServices/Services/Publisher/CreateContact?sessionId={YourSessionId}&contactName={FollowerName}&emailAddress={FollowerEmail}

Body:

  none

Response:

  a contact id (needed for the invite!), `123312-af1341123-coolid`

Send the invite!!

POST

/ShareWebServices/Services/Publisher/CreateSubscriptionInvitation?sessionId={YourSessionId}&contactId={ContactId}

Body:

  {
    "AlertSettings":{
      "HighAlert":{
        "MinValue":200,
        "AlarmDelay":"PT1H",
        "AlertType":1,
        "IsEnabled":false,
        "RealarmDelay":"PT2H",
        "Sound":"High.wav",
        "MaxValue":401
      },
      "LowAlert":{
        "MinValue":39,
        "AlarmDelay":"PT30M",
        "AlertType":2,
        "IsEnabled":false,
        "RealarmDelay":"PT2H",
        "Sound":"Low.wav",
        "MaxValue":70
      },
      "FixedLowAlert":{
        "MinValue":39,
        "AlarmDelay":"PT0M",
        "AlertType":3,
        "IsEnabled":true,
        "RealarmDelay":"PT30M",
        "Sound":"UrgentLow.wav",
        "MaxValue":55
      },
      "NoDataAlert":{
        "MinValue":39,
        "AlarmDelay":"PT1H",
        "AlertType":4,
        "IsEnabled":false,
        "RealarmDelay":"PT0M",
        "Sound":"NoData.wav",
        "MaxValue":401
      }
    },
    "Permissions":1,
    "DisplayName":"{YourAccountDisplayName}"
  }

Note that permissions 1 means they can view your graph data

Response:

  a subscriber id for the person you invited! (Usefull for updating their
  subscription permissions and such) `793312-af1341123-coolid`

List all Followers

POST

/ShareWebServices/Services/Publisher/ListPublisherAccountSubscriptions?sessionId={YourSessionId}

Body:

  none

Response:

  [
    {
      "ContactId":"FollowersContactId",
      "ContactName":"FollowersName",
      "DateTimeCreated":{
        "DateTime":"\/Date(1437101121008)\/",
        "OffsetMinutes":0
      },
      "DateTimeModified":{
        "DateTime":"\/Date(1437101121008)\/",
        "OffsetMinutes":0
      },
      "DisplayName":"YourDisplayName",
      "InviteExpires":{
        "DateTime":"\/Date(1437705921008)\/",
        "OffsetMinutes":0
      },
      "IsEnabled":false,
      "IsMonitoringSessionActive":true,
      "Permissions":1,
      "State":2,
      "SubscriberId":"00000000-0000-0000-0000-000000000000",
      "SubscriptionId":"theirSubscriptionIdIsuppose?"
    }
  ]

note: maybe we can use this subscription id to send our own custom invites to followers

Delete Follower

POST

/ShareWebServices/Services/Publisher/DeleteContact?sessionId={YourSessionId}&contactId={followersContactId}

Body:

  none

Response:

  just a status, `200`

Still undocumented but logged if you need info on it (Not adding it all here out of lazziness)

  • getting the image
  • getting the subscription display name
  • getting the subscription email address
  • reading the contact list
  • sending changes to the contacts Permissions
  • removing a contact
  • follower aknowledging alarms
  • follower reading invitation info
  • follower accepting invitation
  • follower updating runtimeInfo
  • folower listing all their subscriptions
  • read subscription alerts

CURL examples for getting values from Dexcom

curl -v \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -H "User-Agent: Dexcom Share/3.0.2.11 CFNetwork/711.2.23 Darwin/14.0.0" \
  -X POST https://share1.dexcom.com/ShareWebServices/Services/General/LoginPublisherAccountByName \
  -d '{"accountName":"YOURLOGIN","applicationId":"d8665ade-9673-4e27-9ff6-92db4ce13d13","password":"YOURPASSWORD"}' 

which should recieve a response like

"8c1234deb-323c-4e9d-8362-39cfa23499ed"

which you use to get values like

curl -v \
  -H "Content-Length: 0" -H "Accept: application/json" \
  -H "User-Agent: Dexcom Share/3.0.2.11 CFNetwork/672.0.2 Darwin/14.0.0" \
  -X POST "https://share1.dexcom.com/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues?sessionId=8c1234deb-323c-4e9d-8362-39cfa23499ed&minutes=1440&maxCount=1" 
@roger-moulton
Copy link

It looks like pydexcom is on top of the share2 endpoint changes that were made recently. See the changes that were made here. I believe General/LoginPublisherAccountByName endpoint has been deprecated which may be your issue. I have been able to get my glucose readings with the following requests:

Environment Vars

application_id = d8665ade-9673-4e27-9ff6-92db4ce13d13

account_name = your dexcom account name

password = your dexcom password

Get Account ID

Request

curl --location --request POST 'https://share2.dexcom.com/ShareWebServices/Services/General/AuthenticatePublisherAccount' \
--header 'Content-Type: application/json' \
--data-raw '{
    "accountName": "{{account_name}}",
    "password": "{{password}}",
    "applicationId": "{{application_id}}"
}'

Response

{{account_id}}

Get Session ID

Request

curl --location --request POST 'https://share2.dexcom.com/ShareWebServices/Services/General/LoginPublisherAccountById' \
--header 'Content-Type: application/json' \
--data-raw '{
            "accountId": "{{account_id}}",
            "password": "{{password}}",
            "applicationId": "{{application_id}}"
}'

Response

{{session_id}}

Get all glucose readings from last 24 hrs

Request

curl --location --request POST 'https://share2.dexcom.com/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues' \
--header 'Content-Type: application/json' \
--data-raw '{
    "sessionId": "{{session_id}}",
    "minutes": "1440",
    "maxCount": "288"
}'

Response

[
    {
        "WT": "Date(1639936503000)",
        "ST": "Date(1639936503000)",
        "DT": "Date(1639936503000-0500)",
        "Value": 89,
        "Trend": "Flat"
    },

...

    {
        "WT": "Date(1639936204000)",
        "ST": "Date(1639936204000)",
        "DT": "Date(1639936204000-0500)",
        "Value": 87,
        "Trend": "Flat"
    },
]

@kquinnell
Copy link

Thank you all!! I was able to get my program working again.

@racekjak
Copy link

Does anyone know how long does the sessionId stays valid? I am wondering how often do I need to renew it.

@henrik-d
Copy link

@racj01 From my experience a session has a maximum lifetime of 24 hours. But you have to keep the session alive by making requests with it regularly (e.g. every 30 minutes). If you don't use it for a longer time, it will expire earlier than after 24 hours.

@Rytis-J
Copy link

Rytis-J commented Sep 25, 2022

Is there a way to know the timing of glucose reading? Because system time doesn`t say much!

@emavgl
Copy link

emavgl commented Dec 26, 2022

Are you aware of the Receiver / Follower api? I'd like to get the data from the receiver perspective without having access to the publisher credentials.

@thriqon
Copy link

thriqon commented Feb 27, 2023

If anyone else is experiencing failing logins: Apparently Dexcom is limiting actual password length to 20 characters. My password manager is storing the longer form...

@zeletrik
Copy link

Does anyone know hot to acquire own applicationId for the Share API?

@corneliusroemer
Copy link

How can I accept an invitation? The follower app is broken so curl is the only rescue!

@fsallstrom
Copy link

Hi, is anyone else having problems to authenticate user accounts that looks like phone numbers? Many of my users can no longer login after they had to change their dexcom account names to their phone number. Account names that looks like email addresses work just fine. The dexcom server just returns invalid account or password, like if the password was wrong. I have validated a few accounts that they can logon to dexcom clarity just fine, so password is good and account is not locked. I am running out of ideas

@findthebug
Copy link

same issue here. i guess everyone has issues with that at the moment.

@fsallstrom
Copy link

And now I’m getting increasing number of users that report they get no data. I’ve tested with one of the affected user accounts, I can authenticate and login the account but when I request the latest glucose values I get no data back with response code 200. Clearly Dexcom is making some changes in their backend. Has anyone experienced the same?

@fsallstrom
Copy link

fsallstrom commented Nov 6, 2023

If I could, I would try to sniff the comms between the Dexcom follower app and Dexcom share to see if the api has changed. Unfortunately I don’t possess those tech skills needed.

@MooseV2
Copy link

MooseV2 commented Nov 6, 2023

I'm worried the API exposed in this gist might be in the process of being deprecated. I've intercepted the Follow app traffic on iOS by using the app called "Proxyman". The data looks entirely different now; here's a sample (all IDs redacted and some data truncated):

POST to /ShareWebServices/Services/Subscriber/ReadEvents

Request data:

eyJUaW1lc3RhbXAiOiIyMDIzLTExLTA2VDIyOjE2OjM3WiIsIkFwcElkIjoiMjZiMDhjY2MtMjA5
Mi00MmVhLWExZTQtYWUwOTg0MGE4NjQwIiwiSXNaaXAiOjAsIkFjY0lkIjoiYzVjMWZmYTUtY2Fl
Yy00Zjk4LTk3MDEtMTQ5OGE2YTljNmJkIn0K

Which is Base64 for:

{"Timestamp":"2023-11-06T22:16:37Z","AppId":"26b08ccc-2092-42ea-a1e4-ae09840a8640","IsZip":0,"AccId":"c5c1ffa5-caec-4f98-9701-1498a6a9c6bd"}

Every response now has three fields:

  1. IsZip: whether the data is gzip compressed
  2. Hmac: B64 HMAC signature for this message (worrysome if this becomes enforced; we'll need the secret to sign messages)
  3. Content: The actual content

Reponse:

{
"IsZip": true,
"Hmac": "bXlSZXJpYmxlU3RyMG5nTm93PT==",
"Content": "H4sIAAAAAAAA/ .... (long) ... kAAA="
}

I can extract the data by decoding the base64 and un-gzipping the bytes, which thankfully returns JSON like this:

[
  {
    "SubscriptionId": "75442486-0878-440c-9db1-a7006c25a39f",
    "Events": {
      "Glucose": [
        {
          "Timestamp": "2023-11-06T15:40:55-05:00",
          "Value": 178,
          "TrendArrow": "Flat",
          "TrendRate": -0.4,
          "TXId": "ABC123",
          "DT": null,
          "IsBackfilled": false,
          "TXTime": 1874174,
          "AlgState": "InCalibration",
          "TXSessionStartTime": 1697429081,
          "PredictedValue": null,
          "RecordedTime": "2023-11-06T15:41:04-05:00",
          "RecordVersion": "1.0",
          "SecAlgState": null,
          "SourceStream": null,
          "TXSW": null,
          "Status": null,
          "SYNC": "2023-11-06T20:41:06.540224556Z",
          "AccountId": "11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000"
        },
        {
            ...
           // more rows like this
        },

It seems not all endpoints are returning zip data (some are set to isZip: false and the JSON is readable in the response).

I can do more investigation when I get more time, but currently my pydexcom integration is working fine.

@fsallstrom
Copy link

hi MooseV2 thank you so much for taking time and look into this. I share your concerns. It feels like tey are doing work to deprecate this API. As I mentioned, I have had quite a few users contacting me syaing they don't get any data back on my apps (Garmin apps). I can authorize and login their accounts but the call to ReadPublisherLatestGlucoseValues returns with empty data. At least two of the accounts havig issues are in Australia, so could be a gradual roll-out market by market (or just a glitch, I have reached out to the users again to see if they still have the issue).

My own dexcom G6 app (yes, I am a dexcom user myself) got updated last night and when I woke up this morning I noticed I didn't get any data with my own account. That resolved by itself a couple of hours later so could have been a problem with that dexcom update.

But going back to the previous post, l will try to play around some with the new endpoint to see what I can get. Did you manage to capture also the auth/login flow?

Again, thank you for your work!!!

@findthebug
Copy link

findthebug commented Nov 9, 2023

I have also played around a bit and for me the follow app reacts exactly as MooseV2 describes. I use a G7. I've had no results a few times in the last few weeks but only occasionally and for a few minutes only. I use shareous1 and the service (ReadPublisherLatestGlucoseValues) is otherwise very reliable for me. i have seen that dexcom has a status page #https://status.dexcom.com and they reported some outages lately. Also worth mention: #https://status.dexcom.com/api/v2/status.json

i have tried to log the login flow in the iOS dexcom G7 app with proxyman but the app uses SSL pinning which makes it impossible to read the request. i will try again as soon as i have my second iphone to hand. I'm not sure if all apps, G6, G7 android, iOS use SSL pinning?

@MooseV2
Copy link

MooseV2 commented Nov 9, 2023

Here's some Python code which does one of the auth flows for those that want to play with it. For some reason, I'm not currently receiving any data (even with the official Follow app) so I can't investigate further at the moment (Dexcom may be having an outage).

import requests

username = "your username"
password = "your plaintext password"
app_id = "d89443d2-327c-4a6f-89e5-496bbb0317db" # Where's this from?
server = "https://dexauth-eu.herokuapp.com" # dexauth-us in the US?
share_server = "https://shareous1.dexcom.com" # share1.dexcom.com in the US? 

def post(*args, **kwargs):
    response = requests.post(*args, **kwargs)
    print("POST", response.url, response.status_code)
    print(response.json())
    print()
    return response.json()

json = {
    "accountName": username,
    "applicationId": app_id,
    "password": password
}
# Get the account ID

data = post(f"{server}/ShareWebServices/Services/General/AuthenticatePublisherAccount", json=json)

account_id = data["SessionID"]

# Log in using that account ID
json = {
    "accountId": account_id,
    "applicationId": app_id,
    "password": password
}
data = post(f"{server}/ShareWebServices/Services/General/LoginPublisherAccountById", json=json)
session_id = data["SessionID"]

url = (f"{share_server}"
       "/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues?"
       f"sessionID={session_id}&minutes=1440&maxCount=5")

post(url)

If anyone is able to get this working with a problematic (eg phone number based) login, that would be interesting. My login has always been a regular username so the testing I can do is limited to that.

Additionally, I was able to identify another login/auth at https://uam2.dexcom.com/identity/connect/token but I haven't yet figured out how it works. It uses UUIDs instead of usernames, passwords, etc. Not sure what that's about -- it might be the actual Dexcom app sending that traffic (not Follow) though since I have both on my phone.

@fsallstrom
Copy link

Hi, I am still receiving loads of feedback from my users that they get no data. In some cases it solved by itself after a few hours (like it did for me), in some cases they don't see any recent data in their follower apps (dexcom outage), and in some cases they have data in their follower app but don't get any data through my app.

A few users have reported that they got it back to work after disabling share in their dexcom app and then re enabled it. Clearly Dexcom is doing something, maybe ramping up their new share API and having issues with it?

@MooseV2 thank you for sharing your code sample. I see a reference to dexauth-eu.herukoapp.com in your code:
server = "https://dexauth-eu.herokuapp.com" # dexauth-us in the US?
That is my proxy server that I created to proxy requests for my Garmin apps. Garmin devices does not accept the way the sessionID is formatted by the dexcom share server so I need to reformat it with the proxy. Quite annyoing.

How did you find out the url to it? I thought my Garmin apps were the only ones using it.

@KajBjurman
Copy link

hi MooseV2 thank you so much for taking time and look into this. I share your concerns. It feels like tey are doing work to deprecate this API. As I mentioned, I have had quite a few users contacting me syaing they don't get any data back on my apps (Garmin apps). I can authorize and login their accounts but the call to ReadPublisherLatestGlucoseValues returns with empty data.

A lot of people who use iPhone as main phone have/had problems with sharing after an update. The fix for them was to turn sharing off for a while, reboot the phone (don't know if that was required), and then enable sharing again. Looks like the setting for sharing wasn't in sync anymore. It looked like sharing was on, but it was off. This also affected the regular follow app.

@MooseV2
Copy link

MooseV2 commented Nov 10, 2023

@MooseV2 thank you for sharing your code sample. I see a reference to dexauth-eu.herukoapp.com in your code: server = "https://dexauth-eu.herokuapp.com" # dexauth-us in the US? That is my proxy server that I created to proxy requests for my Garmin apps. Garmin devices does not accept the way the sessionID is formatted by the dexcom share server so I need to reformat it with the proxy. Quite annyoing.
How did you find out the url to it? I thought my Garmin apps were the only ones using it.

Oh my, that's hilarious and embarrassing. I have your Dexcom watch face installed on my Fenix 6, so I must have accidentally captured that traffic while trying to observe the Follow app on my iPhone. The flaws in doing things without isolation I suppose.

That makes a lot of sense why it's not returning data, then. 😆 I'll go back to the drawing board with more investigation.

As an aside, thanks so much for making that watch face. It's been my daily driver for the past 3 years and one of my favourite things to show off when people ask me about my watch.

@fsallstrom
Copy link

:-) Glad you like it, @MooseV2 Have you considered to develop any Garmin apps yourself?

I have instructed users to disable/re-enable sharing in their Dexcom app, it seems to be working for some users but not all.

@fsallstrom
Copy link

Ok, so I played around a bit trying to get something back from the /ShareWebServices/Services/Subscriber/ReadEvents endpoint. I am using the request module in js, here is what the request looks like:

Fetch request {
  uri: 'https://shareous1.dexcom.com/ShareWebServices/Services/Subscriber/ReadEvents',
  body: 'eyJUaW1lc3RhbXAiOiIyMDIzLTExLTExVDE2OjE2OjM3WiIsIkFwcElkIjoiMjZiMDhjY2MtMjA5Mi00MmVhLWExZTQtYWUwOTg0MGE4NjQwIiwiSXNaaXAiOjAsIkFjY0lkIjoiMjc5ZTRjYWUtYWMwNC00ZjFmLTllM2UtMzA2MjRmOWQ5MTk0In0=',
  json: true,
  headers: {
    'User-Agent': 'Dexcom%20Share/3.0.2.11 CFNetwork/711.2.23 Darwin/14.0.0',
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'Content-Length': '0'
  },
  method: 'POST',
  rejectUnauthorized: false
} 

I base64 encoded this string:
{"Timestamp":"2023-11-11T16:16:37Z","AppId":"26b08ccc-2092-42ea-a1e4-ae09840a8640","IsZip":0,"AccId":"aba9e4a0-1ca9-4fe3-8ebd-62226462c10f"}
and put it in the request body. Did not work.

@MooseV2
Copy link

MooseV2 commented Nov 12, 2023

:-) Glad you like it, @MooseV2 Have you considered to develop any Garmin apps yourself?
Indeed -- the first thing I did when I got the Garmin was mess around with the SDK since I wanted to view Dexcom on it. I was happy to see it already existed!

I base64 encoded this string: [...]

Okay, so I've discovered a bit more about the body:

  1. You have to send it with quotes around the base64 string.
"eyJUaW1lc3RhbXAiOiIyMDIzLTExLTE"

instead of

eyJUaW1lc3RhbXAiOiIyMDIzLTExLTE
  1. There are three sections separated by periods (.).
Part 1:
{"IsZip":0,"Timestamp":"2023-11-12T05:36:37Z","AccId":"14edc443-6d10-40a0-ae1c-f564b0ef9ebe","AppId":"d89443d2-327c-4a6f-89e5-496bbb0317db"}

Part 2:
{"SubscriptionLastSyncTimestamps":[{"SubscriptionId":"3e39166b-98be-416d-9ee9-a4ed3d89d0d8","LastSyncTimestamps":{"Glucose":"2023-11-08T22:49:04Z"}}]}

Part 3:
Some sort of HMAC? This will probably be tricky to decode. Here are some examples:

L7DxdcLz0ntiU5p2W0vvsd
N4r7x-9dyEJ03YqsTXHL5d
95eNUm8_1KwjUaW2YLg8Td

So base64 the first two and concatenate (important: remove the equal '=' characters from the base64 string), and you'll end up with a string like this (again, wrapped in quotation marks):

"eyJJc1ppcCI6MCwiVGltZXN0YW1wIjoiMjAyMy0xMS0xMlQwNTozNjozN1oiLCJBY2NJZCI6IjE0ZWRjNDQzLTZkMTAtNDBhMC1hZTFjLWY1NjRiMGVmOWViZSIsIkFwcElkIjoiZDg5NDQzZDItMzI3Yy00YTZmLTg5ZTUtNDk2YmJiMDMxN2RiIn0.eyJTdWJzY3JpcHRpb25MYXN0U3luY1RpbWVzdGFtcHMiOlt7IlN1YnNjcmlwdGlvbklkIjoiM2UzOTE2NmItOThiZS00MTZkLTllZTktYTRlZDNkODlkMGQ4IiwiTGFzdFN5bmNUaW1lc3RhbXBzIjp7IkdsdWNvc2UiOiIyMDIzLTExLTA4VDIyOjQ5OjA0WiJ9fV19.T_t7EeMlW32KE5dvVpgzvd"

The HMAC part is Base64URL encoded, which is basically just Base64 but "+" becomes "-" and "/" becomes "_". When decoded, it becomes 16 bytes. If you send the wrong value here, you get the error:

'Code': 'IntegrityCheckFailed', 'Message': "Request signature doesn't match device key. [...]"

Could be related to either a) the device key found from /ShareWebServices/Services/Subscriber/DeviceKeys, or b) the device token from [...]/Subscriber/ReadSubscriber2?

It also has something to do with the Account ID, and I've been able to glean this nonworking pseudocode:

import hmac
import hashlib
import base64
import uuid


def generate_hmac(data, key):
    """Generate a SHA256 HMAC and encode as base64."""
    hmac_obj = hmac.new(key.encode("utf-8"), data.encode("utf-8"), hashlib.sha256)
    return base64.b64encode(hmac_obj.digest()).decode().replace("=", "")


def create_trailing_hmac(account_id, object2):
    account_id_str = str(account_id)
    object2_str = str(object2)

    intermediate_hash = generate_hmac(account_id_str + object2_str, object2_str)
    final_hash = generate_hmac(account_id_str + intermediate_hash, object2_str)
    result = account_id_str + final_hash
    return result


# Example usage
account_id = uuid.UUID("14edc443-6d10-40a0-ae1c-f564b0ef9ebe")
object2 = uuid.UUID("b85a7d6e-ede9-46c7-a6ac-fb764c31f9b6") # Not sure?

first, second, mac = string.split(".")
print(generate_hmac(first + second, create_trailing_hmac(account_id, object2)))

I'm staring at a disassembly but obviously I'm missing something since that code doesn't work. HMACs are usually 64 bytes but the request MAC is 16 bytes... Hmm.

Anyway, maybe this will help someone...

@KajBjurman
Copy link

Hm, that seems to be similar in structure to a JWT, where the last part should be signature. What if it isn't a signature, but just a hash, possibly with a salt? MD5 is usually only 16 bytes.

@MooseV2
Copy link

MooseV2 commented Dec 27, 2023

Just a small update: it appears the Dexcom outage has been resolved and the old API is working again. I'll keep trying to reverse engineer the new API since I suspect they may push towards it, but is anyone still experiencing issues with the old one?

Nb: It's a bit ridiculous that a bioengineering/health company can have a two month outage, especially without any sort of timeline or communication... at my company I would be fired faster than I could explain why we only had 80% uptime on a relatively critical production component.

@Okomoko
Copy link

Okomoko commented Jan 6, 2024

:-) Glad you like it, @MooseV2 Have you considered to develop any Garmin apps yourself?
Indeed -- the first thing I did when I got the Garmin was mess around with the SDK since I wanted to view Dexcom on it. I was happy to see it already existed!

I base64 encoded this string: [...]

Okay, so I've discovered a bit more about the body:

  1. You have to send it with quotes around the base64 string.
"eyJUaW1lc3RhbXAiOiIyMDIzLTExLTE"

instead of

eyJUaW1lc3RhbXAiOiIyMDIzLTExLTE
  1. There are three sections separated by periods (.).
Part 1:
{"IsZip":0,"Timestamp":"2023-11-12T05:36:37Z","AccId":"14edc443-6d10-40a0-ae1c-f564b0ef9ebe","AppId":"d89443d2-327c-4a6f-89e5-496bbb0317db"}

Part 2:
{"SubscriptionLastSyncTimestamps":[{"SubscriptionId":"3e39166b-98be-416d-9ee9-a4ed3d89d0d8","LastSyncTimestamps":{"Glucose":"2023-11-08T22:49:04Z"}}]}

Part 3:
Some sort of HMAC? This will probably be tricky to decode. Here are some examples:

L7DxdcLz0ntiU5p2W0vvsd
N4r7x-9dyEJ03YqsTXHL5d
95eNUm8_1KwjUaW2YLg8Td

So base64 the first two and concatenate (important: remove the equal '=' characters from the base64 string), and you'll end up with a string like this (again, wrapped in quotation marks):

"eyJJc1ppcCI6MCwiVGltZXN0YW1wIjoiMjAyMy0xMS0xMlQwNTozNjozN1oiLCJBY2NJZCI6IjE0ZWRjNDQzLTZkMTAtNDBhMC1hZTFjLWY1NjRiMGVmOWViZSIsIkFwcElkIjoiZDg5NDQzZDItMzI3Yy00YTZmLTg5ZTUtNDk2YmJiMDMxN2RiIn0.eyJTdWJzY3JpcHRpb25MYXN0U3luY1RpbWVzdGFtcHMiOlt7IlN1YnNjcmlwdGlvbklkIjoiM2UzOTE2NmItOThiZS00MTZkLTllZTktYTRlZDNkODlkMGQ4IiwiTGFzdFN5bmNUaW1lc3RhbXBzIjp7IkdsdWNvc2UiOiIyMDIzLTExLTA4VDIyOjQ5OjA0WiJ9fV19.T_t7EeMlW32KE5dvVpgzvd"

The HMAC part is Base64URL encoded, which is basically just Base64 but "+" becomes "-" and "/" becomes "_". When decoded, it becomes 16 bytes. If you send the wrong value here, you get the error:

'Code': 'IntegrityCheckFailed', 'Message': "Request signature doesn't match device key. [...]"

Could be related to either a) the device key found from /ShareWebServices/Services/Subscriber/DeviceKeys, or b) the device token from [...]/Subscriber/ReadSubscriber2?

It also has something to do with the Account ID, and I've been able to glean this nonworking pseudocode:

import hmac
import hashlib
import base64
import uuid


def generate_hmac(data, key):
    """Generate a SHA256 HMAC and encode as base64."""
    hmac_obj = hmac.new(key.encode("utf-8"), data.encode("utf-8"), hashlib.sha256)
    return base64.b64encode(hmac_obj.digest()).decode().replace("=", "")


def create_trailing_hmac(account_id, object2):
    account_id_str = str(account_id)
    object2_str = str(object2)

    intermediate_hash = generate_hmac(account_id_str + object2_str, object2_str)
    final_hash = generate_hmac(account_id_str + intermediate_hash, object2_str)
    result = account_id_str + final_hash
    return result


# Example usage
account_id = uuid.UUID("14edc443-6d10-40a0-ae1c-f564b0ef9ebe")
object2 = uuid.UUID("b85a7d6e-ede9-46c7-a6ac-fb764c31f9b6") # Not sure?

first, second, mac = string.split(".")
print(generate_hmac(first + second, create_trailing_hmac(account_id, object2)))

I'm staring at a disassembly but obviously I'm missing something since that code doesn't work. HMACs are usually 64 bytes but the request MAC is 16 bytes... Hmm.

Anyway, maybe this will help someone...

I am looking for a solution to extract the very last insulin injection event (type of insulin, amount of injection and the timestamp) from Dexcom. @MooseV2, I am wondering if the endpoint you captured can be used for that purpose, any chance you could help me on that? There is an official endpoint already published in their public API's but I have been already using pydexcom for a long while and not willing to start from scratch.

Thanks

@jazeee
Copy link

jazeee commented Jan 6, 2024 via email

@KajBjurman
Copy link

KajBjurman commented Jan 6, 2024

It's kind of a JWT, but they either have a bug, or don't care about being fully compliant. The header doesn't static algorithm, so we don't know how the produce the signature, but the signature is also encoded incorrectly. The signature should be encoded in base 64, without padding, and with "url safe" mode, but we can see here, and I also saw in one of my captures, that the signature contained an underscore, which is invalid.

@fsallstrom
Copy link

Just a small update: it appears the Dexcom outage has been resolved and the old API is working again. I'll keep trying to reverse engineer the new API since I suspect they may push towards it, but is anyone still experiencing issues with the old one?

Nb: It's a bit ridiculous that a bioengineering/health company can have a two month outage, especially without any sort of timeline or communication... at my company I would be fired faster than I could explain why we only had 80% uptime on a relatively critical production component.

I don't get any more emails from users about the issue so it seems to be resolved across all/most markets.

@osa1
Copy link

osa1 commented Apr 25, 2024

Sorry for the noise -- it turns out Dexcom Android app logged me out from Dexcom services and stopped uploading data.

I also fixed my script in the meantime. Here's a working version: https://github.com/osa1/Dextrack/blob/main/tools/get_bg_levels.py

(Deleted my original comment to avoid adding noise to this useful page)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment