Skip to content

Networking

CT5 edited this page Oct 22, 2026 · 6 revisions

Networking

This doc will outline how bitwarden sends requests online.

Endpoints

Bitwarden has 4 endpoints:

  • Main Endpoint (https://vault.bitwarden.com)
  • API Endpoint (https://api.bitwarden.com)
  • Vault Endpoint (https://vault.bitwarden.com)
  • Icon Endpoint (https://icons.bitwarden.net)
  • Websocket Endpoint (wss://notifications.bitwarden.com)

Unlike Bitwarden, VaultWarden uses the same URL for the main, API and vault endpoint (https://someVault.com). VaultWarden uses the same Bitwarden icon endpoint but has a slightly different websocket endpoint (wss://someVault.com/notifications).

Sending Requests

Bitwarden requires a valid useragent or it will result in random weird responses (like the kdfIterations always being at 600 000). Bitwarden also requires a Bitwarden-Client-Name which can be a web or desktop as well as a Bitwarden-Client-Version which looks like 2026.4.1. When sending requests, Bitwarden usually requires a authorization key as well unless you are trying to get an access token. All Bitwarden requests return with a 200 except for the WebSocket which returns a 101.

Prelogin Request

When logging into an account, Bitwarden sends a POST Prelogin password request to /identity/accounts/prelogin/password with a JSON email. Then it recieves a JSON response like:

{
    "kdf": 0, // Or 1 for Argon2ID
    "kdfIterations": 600001,
    "kdfMemory": null,
    "kdfParallelism": null
}

Known Device Request

After sending a prelogin request upon login, it sends a GET request to /api/devices/knowndevice with a simple true or false response.

Retrieving a token

Fresh Tokens

To retrieve a token upon login, VaultWarden sends a POST request to /identity/connect/token with a application/x-www-form-urlencoded; charset=utf-8 that contains:

  • scope: api offline_access
  • client_id: web
  • deviceType: 10
  • deviceIdentifier: uniqueGUID
  • deviceName: browser (firefox)
  • grant_type: password
  • username: email (a@a.com)
  • password: Master Password Hash

The POST request then returns a JSON response with the Key, Kdf, KdfIterations, KdfMemory, KdfParallelism, access_token, expires_in and refresh_token.

Using Refresh Token

To retrieve a token with a refreshToken, send a POST request to identity/connect/token and send a JSON request with the grant_type (refresh_token), client_id (web) and refresh_token which will pass back a JSON response with the access_token, expires_in and refresh_token.

Logging In

Prelogin Request

When logging in, Bitwarden sends a POST request to <VWURL>/identity/accounts/prelogin/password with the email as the request in JSON format. It then returns a JSON with the following information which will be required to decrypt the vault and make the masterPasswordHash. The response looks something like this:

{
    "kdf": 0,
    "kdfIterations": <KDF ITERATIONS>,
    "kdfMemory": null, // Used for Argon2ID
    "kdfParallelism":null // Used for Argon2ID
}

Note

If you provide an incorrect useragent, or an invalid one, you will always get 600 000 iterations or the request will fail.

Known device request

It then sends a GET request to <VWURL>/api/devices/knowndevice with no request. Bitwarden will then return a true or false response. It isnt very clear what this is used for.

New Token Request

Upon submitting the password, Bitwarden send a whole ton of requests.
First, it sends a POST request to <VWURL>/identity/connect/token with a content-type of application/x-www-form-urlencoded; charset=utf-8, with the following as the request data:

  • scope "api offline_access"
  • client_id "web"
  • deviceType "10"
  • deviceIdentifier <Random GUID>
  • deviceName "firefox"
  • grant_type "password"
  • username <Email>
  • password <MasterPasswordHash>

Remember that the client_id can be web, desktop, etc and the deviceType must be one of the following (from Bitwarden Github):

export enum DeviceType {
  Android = 0,
  iOS = 1,
  ChromeExtension = 2,
  FirefoxExtension = 3,
  OperaExtension = 4,
  EdgeExtension = 5,
  WindowsDesktop = 6,
  MacOsDesktop = 7,
  LinuxDesktop = 8,
  ChromeBrowser = 9,
  FirefoxBrowser = 10,
  OperaBrowser = 11,
  EdgeBrowser = 12,
  IEBrowser = 13,
  UnknownBrowser = 14,
  AndroidAmazon = 15,
  UWP = 16,
  SafariBrowser = 17,
  VivaldiBrowser = 18,
  VivaldiExtension = 19,
  SafariExtension = 20,
  SDK = 21,
  Server = 22,
  WindowsCLI = 23,
  MacOsCLI = 24,
  LinuxCLI = 25,
  DuckDuckGoBrowser = 26,
}

Bitwarden then responds with a JSON response with the following

{
    "AccountKeys": {
        "Object": "privateKeys",
        "publicKeyEncryptionKeyPair": {
            "Object": "publicKeyEncryptionKeyPair",
            "publicKey": "...",
            "wrappedPrivateKey": "..."
        }
    },
    "ForcePasswordReset": false,
    "Kdf": 0,
    "KdfIterations": <KDF ITERATIONS>,
    "KdfMemory": null,
    "KdfParallelism": null,
    "Key": "<VAULT Key>",
    "MasterPasswordPolicy": {
        "Object": "masterPasswordPolicy"
    },
    "PrivateKey": "...",
    "ResetMasterPassword": false,
    "UserDecryptionOptions": {
        "HasMasterPassword": true,
        "MasterPasswordUnlock": {
            "Kdf": {
                "Iterations": 600001,
                "KdfType": 0,
                "Memory": null,
                "Parallelism": null
            },
            "MasterKeyEncryptedUserKey": "...",
            "MasterKeyWrappedUserKey": "...",
            "Salt": "a@a.com"
        },
        "Object": "userDecryptionOptions"
    },
    "access_token": "<ACCESS TOKEN>",
    "expires_in": <EXPIRY NUM>,
    "refresh_token": "<REFRESH TOKEN>",
    "scope": "api offline_access",
    "token_type": "Bearer"
}

Config Request

VaultWarden then sends a GET request to <VWURL>/api/config with a JSON response like this:

{
    "environment": {
        "api": ...,
        "cloudRegion": null,
        "identity": ...,
        "notifications": ...,
        "sso": "",
        "vault": ...
    },
    "featureStates": {
        "pm-19148-innovation-archive": true
    },
    "gitHash": "f21a3ada",
    "object": "config",
    "push": {
        "pushTechnology": 0,
        "vapidPublicKey": null
    },
    "server": {
        "name": "Vaultwarden",
        "url": "https://github.com/dani-garcia/vaultwarden"
    },
    "settings": {
        "disableUserRegistration": false
    },
    "version": "2025.12.0"
}

Sync Request

VaultWarden then sends a GET request to <VWURL>/api/sync which returns a JSON response like this:

{
  "ciphers": [
    {
      "archivedDate": "2026-08-10T07:52:03.231119Z",
      "attachments": [
        {
          "fileName": "2....",
          "id": "XXXXXXXXXXXXXXXXXXXX",
          "key": "2....",
          "object": "attachment",
          "size": "<NUM>>",
          "sizeName": "XX.XX KB",
          "url": "https://<URL>/attachments/<UUID>/<AttachmentID>?token=<TOKEN>"
        }
      ],
      "card": null,
      "collectionIds": [],
      "creationDate": "2026-07-31T06:51:25.742018Z",
      "data": {
        ... // REMEMBER: Bitwarden requires a String as Data
      },
      "deletedDate": "2026-08-10T07:48:04.106107Z",
      "edit": true,
      "favorite": false,
      "fields": [
        {
          "linkedId": null,
          "name": "...",
          "type": 0, // 0 for text, 1 for hidden, 2 for checkbox, 3 for linked
          "value": "..."
        }
      ],
      "folderId": null,
      "id": "<RANDOM GUID>",
      "identity": null,
      "key": "...",
      "login": {
        "autofillOnPageLoad": null,
        "fido2Credentials": [
          {
            "counter": "2.XXX",
            "creationDate": "<DATE>",
            "credentialId": "2.XXX",
            "discoverable": "2.XXX",
            "keyAlgorithm": "2.XXX",
            "keyCurve": "2.XXX",
            "keyType": "2.XXX",
            "keyValue": "2.XXX",
            "response": null,
            "rpId": "2.XXX",
            "rpName": "2.X",
            "userDisplayName": "2.XXX",
            "userHandle": "2.XXX",
            "userName": "2.XXX"
          }
        ]
        "password": "...",
        "passwordRevisionDate": null,
        "totp": "...",
        "uri": "...",
        "uris": [
          {
            "match": null,
            "uri": "...",
            "uriChecksum": "..."
          }
        ],
        "username": "..."
      },
      "name": "...",
      "notes": "...",
      "object": "cipherDetails",
      "organizationId": null,
      "organizationUseTotp": true,
      "passwordHistory": [],
      "permissions": {
        "delete": true,
        "restore": true
      },
      "reprompt": 0,
      "revisionDate": "2026-08-10T07:48:34.271624Z",
      "secureNote": null,
      "sshKey": null,
      "type": 1,
      "viewPassword": true
    }
  ],
  "collections": [],
  "deletedCiphers": [],
  "deletedFolders": [],
  "domains": {
    ...
  },
  "folders": [
    {
      "id": "<RANDOM GUID>",
      "name": "...",
      "object": "folder",
      "revisionDate": "2026-07-31T06:46:39.236881Z"
    }
  ],
  "object": "sync",
  "policies": [],
  "profile": {
    "_status": 0,
    "avatarColor": "#8200db",
    "creationDate": "2026-04-17T05:22:32.690513Z",
    "culture": "en-US",
    "email": "<EMAIL>",
    "emailVerified": true,
    "forcePasswordReset": false,
    "id": "<RANDOM GUID>",
    "key": "...",
    "name": "<Name>",
    "object": "profile",
    "organizations": [],
    "premium": true,
    "premiumFromOrganization": false,
    "privateKey": "...",
    "providerOrganizations": [],
    "providers": [],
    "securityStamp": "<RANDOM GUID>",
    "twoFactorEnabled": false,
    "usesKeyConnector": false
  },
  "sends": [],
  "userDecryption": {
    "masterPasswordUnlock": {
      "kdf": {
        "iterations": <ITERATIONS>,
        "kdfType": 0,
        "memory": null,
        "parallelism": null
      },
      "masterKeyEncryptedUserKey": "...",
      "masterKeyWrappedUserKey": "...",
      "salt": "<EMAIL>"
    }
  }
}

This vault JSON is used to sync vault ciphers or vault keys.

Websocket

After getting the vault, VaultWarden then establishes a connection with the WebSocket URL <VWURL>/notifications/hub?access_token=.... Here are the notify types:

  • 0: Cipher is updated
  • 1: Cipher is created
  • 2: Cipher is deleted
  • 3: Folder is deleted
  • 4: All Ciphers changed
  • 5: Whole vault changed
  • 6: Org Keys Changed
  • 7: Folder is created
  • 8: Folder is updated
  • 9: Cipher is binned
  • 10: Account settings changed
  • 11: Logout

Profile Request

It then sends a GET request to <VWURL>/api/accounts/profile which returns a JSON response:

{
    "_status": 0,
    "avatarColor": "#8200db",
    "creationDate": "2026-04-17T05:22:32.690513Z",
    "culture": "en-US",
    "email": <EMAIL>,
    "emailVerified": true,
    "forcePasswordReset": false,
    "id": <UNIQUE GUID>,
    "key": "...",
    "name": <NAME>,
    "object": "profile",
    "organizations": [],
    "premium": true,
    "premiumFromOrganization": false,
    "privateKey": "...",
    "providerOrganizations": [],
    "providers": [],
    "securityStamp": <UNIQUE GUID>,
    "twoFactorEnabled": false,
    "usesKeyConnector": false
}

Revision Date Request

After that, it sends a GET request to <VWURL>/api/accounts/revision-date that returns a number

Pending Request

It then sends a GET request to <VWURL>/api/auth-requests/pending which returns a JSON response like the following:

{
    "continuationToken": null,
    "data": [],
    "object": "list"
}

Unlocking

First, VaultWarden retrieves a new accessToken via Refresh Token. Then it re-sync's the vault using a Sync Request and then retrieves profile info using a Profile Request. It then gets the revision date and pending info using a Revision Date Request and a Pending Request.

Clone this wiki locally