---
title: "Upgrade to identity tokens"
description: "What changed from the user hash to identity tokens, why, and how to move an existing integration over step by step."
url: https://docs.gleap.ai/en/articles/16752008-upgrade-to-identity-tokens
collection: "Guides"
updated: 2026-11-07
language: en
---

# Upgrade to identity tokens

What changed from the user hash to identity tokens, why, and how to move an existing integration over step by step.

Gleap now signs your users in with **identity tokens**: a short JWT your backend signs with an identity key. It replaces the user hash, the HMAC of the user ID that your backend used to compute with the project secret. This guide explains what changed, why, and how to move an existing integration over. For the full reference, see [Customer identity](https://docs.gleap.ai/en/articles/709-enforce-identity-verification.md).

## Why we changed it

The user hash proved one thing: the user ID. Everything else your app sent next to it (email, company, plan, billing IDs) could be changed by anyone who called `identify` in the browser. Identity tokens close that gap.

<table style="min-width: 75px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p></p></th><th colspan="1" rowspan="1"><p>User hash</p></th><th colspan="1" rowspan="1"><p>Identity token</p></th></tr><tr><td colspan="1" rowspan="1"><p>Proves</p></td><td colspan="1" rowspan="1"><p>The user ID only</p></td><td colspan="1" rowspan="1"><p>User ID, email, company and any attribute you sign</p></td></tr><tr><td colspan="1" rowspan="1"><p>Secret</p></td><td colspan="1" rowspan="1"><p>The project secret, which also authenticated the server API</p></td><td colspan="1" rowspan="1"><p>Identity keys used only for this, with rotation, retirement and per-platform scope</p></td></tr><tr><td colspan="1" rowspan="1"><p>Expiry</p></td><td colspan="1" rowspan="1"><p>Never</p></td><td colspan="1" rowspan="1"><p><code>exp</code> on every token, plus a session grant that ends after <strong>Signed login lasts</strong></p></td></tr><tr><td colspan="1" rowspan="1"><p>Field protection</p></td><td colspan="1" rowspan="1"><p>None</p></td><td colspan="1" rowspan="1"><p><strong>Only signed updates</strong> per field</p></td></tr><tr><td colspan="1" rowspan="1"><p>Enforcement</p></td><td colspan="1" rowspan="1"><p>One switch for all platforms</p></td><td colspan="1" rowspan="1"><p><strong>Require a signature</strong> per platform</p></td></tr><tr><td colspan="1" rowspan="1"><p>Kai and tools</p></td><td colspan="1" rowspan="1"><p>No notion of a verified customer</p></td><td colspan="1" rowspan="1"><p>Verified customers per conversation, with an email code as the fallback</p></td></tr><tr><td colspan="1" rowspan="1"><p>Help center and portal</p></td><td colspan="1" rowspan="1"><p>Separate sign-in</p></td><td colspan="1" rowspan="1"><p>The same sign-in as the widget</p></td></tr></tbody></table>

## What happens to your project

-   **Existing projects** keep working exactly as before until you switch them. The **Security** page shows a **Classic** card with **Switch to signed identities**.
    
-   **Switching** a project to signed identities (the switch, or creating the first identity key) turns on session grants, protected fields and the 14-day history limit for unsigned users. User hashes stay accepted while **Accept legacy user hash** is on, so nothing breaks the day you switch.
    
-   **New projects** start with signed identities and accept identity tokens only.
    

## Upgrade step by step

1.  **Update the Gleap SDKs** in your apps: JavaScript 19.5.0 or newer, and 19.4.0 or newer for iOS, Android, React Native, Flutter and Capacitor. Keep sending the user hash for now.
    
2.  **Switch the project** under **Settings → Security → Customer identity**. Identified users without a valid signature, including older app versions, now see only the last 14 days of conversations.
    
3.  **Create an identity key** and store its key ID and secret in your backend. The first key protects email, phone, company ID, company name and the billing and shop IDs. In **Settings → Inbox & tickets → Data attributes**, turn **Only signed updates** off for any field your apps still send without a token.
    
4.  **Sign tokens on your backend** and ship an app version that calls `setUserJwt(token)` after login and on every app start, with a `setUserJwtProvider` for renewal. See [Sign the token on your backend](https://docs.gleap.ai/en/articles/709-enforce-identity-verification.md).
    
5.  **Watch Signed logins** on the Security page. When no app sends the user hash any more (the page suggests it after 7 days without one), switch **Accept legacy user hash** off.
    
6.  **Require a signature** on each platform once every app version you support there sends a token.
    

## Old calls and their replacements

<table style="min-width: 50px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Before</p></th><th colspan="1" rowspan="1"><p>Now</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>Gleap.identify(id, data, userHash)</code></p></td><td colspan="1" rowspan="1"><p><code>await Gleap.setUserJwt(token)</code>; put the data into the token</p></td></tr><tr><td colspan="1" rowspan="1"><p>iOS <code>identifyContact(id, andData:, andUserHash:)</code></p></td><td colspan="1" rowspan="1"><p><code>Gleap.setUserJwt(token)</code></p></td></tr><tr><td colspan="1" rowspan="1"><p>Android <code>sessionProperties.setHash(hash)</code></p></td><td colspan="1" rowspan="1"><p><code>Gleap.getInstance().setUserJwt(token)</code></p></td></tr><tr><td colspan="1" rowspan="1"><p>React Native <code>identifyWithUserHash(id, data, hash)</code></p></td><td colspan="1" rowspan="1"><p><code>await Gleap.setUserJwt(token)</code></p></td></tr><tr><td colspan="1" rowspan="1"><p>Flutter <code>identify(..., userHash: hash)</code></p></td><td colspan="1" rowspan="1"><p><code>await Gleap.setUserJwt(token: token)</code></p></td></tr><tr><td colspan="1" rowspan="1"><p>Capacitor <code>identify({ userId, userHash })</code></p></td><td colspan="1" rowspan="1"><p><code>await Gleap.setUserJwt({ token })</code></p></td></tr><tr><td colspan="1" rowspan="1"><p>HMAC-SHA256 of the user ID with the project secret</p></td><td colspan="1" rowspan="1"><p>HS256 JWT signed with an identity key, <code>kid</code> in the header</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Enforce identity verification</strong></p></td><td colspan="1" rowspan="1"><p><strong>Require a signature</strong> per platform</p></td></tr><tr><td colspan="1" rowspan="1"><p>Project secret as <code>Api-Token</code> for the server API</p></td><td colspan="1" rowspan="1"><p>A REST API key (<strong>Settings → Security → REST API keys</strong>)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Help center and portal SSO with a derived signing key</p></td><td colspan="1" rowspan="1"><p>An identity token with <code>aud: "sso"</code></p></td></tr></tbody></table>

Data you used to send unsigned next to the hash (plan, company, custom data) can stay in `updateContact`, as long as the field isn't protected. Values you want to rely on belong in the token.

## Good to know

-   **Older app versions** keep working while **Accept legacy user hash** is on, but their sessions count as unsigned: a 14-day history, Kai treats them as unverified, and protected fields stay as they are. An older SDK can also send a token in place of the hash; that sign-in is verified, but the session stays unsigned because those versions don't send the grant.
    
-   **Cordova, .NET MAUI, Unity and Windows** have no `setUserJwt` yet. Pass the token where they took the hash.
    
-   **WordPress plugin logins** are signed with the user hash until the plugin's next release. Keep **Accept legacy user hash** on until then.
    
-   **Classic projects** that never switch keep the old behaviour, including the user hash and **Enforce identity verification**.

## Related

- [Customer identity](https://docs.gleap.ai/en/articles/709-customer-identity.md)
- [Authenticated conversation files](https://docs.gleap.ai/en/articles/710-authenticated-conversation-files.md)
- [Data regions](https://docs.gleap.ai/en/articles/711-data-regions.md)
- [Connect the Gleap MCP](https://docs.gleap.ai/en/articles/712-connect-the-gleap-mcp.md)
- [Plans and AI pricing](https://docs.gleap.ai/en/articles/713-plans-and-ai-pricing.md)
