What it does
This is the missing piece between "I have a clientId/clientSecret" and "I have a working B24OAuth client". An OAuth-installed Bitrix24 app receives ONAPPINSTALL, ONAPPUPDATE, and ONAPPUNINSTALL events to your configured handler URL. This recipe:
- Receives the install event and persists the OAuth tokens per portal (
member_idas the key). - Exposes
clientForMember(memberId)— builds aB24OAuthinstance from stored tokens, attaches a refresh callback so the next refreshed pair is written back to storage automatically. - Removes credentials on uninstall.
- Demonstrates a per-portal REST call (
profile) through the resulting client.
Use this as the scaffold for a multi-tenant backend that serves many Bitrix24 portals from one OAuth app.
Stack
Node.js 18+, express.
Install
pnpm add express
Environment
export PORT=3001 # default
export B24_CLIENT_ID='local.abc123' # from your Bitrix24 dev console
export B24_CLIENT_SECRET='...'
In the Bitrix24 dev console:
- Install handler URL:
https://your-server.example.com/install - Uninstall handler URL:
https://your-server.example.com/uninstall
For local development, expose with npx ngrok http 3001.
Run
npx tsx 12-oauth-install.ts
Then trigger the handshake by installing the app on a Bitrix24 portal. Verify with:
curl https://your-server.example.com/portal/<memberId>/profile
Source
skills/b24jssdk-recipes/examples/12-oauth-install.ts.
Notes
- Persistence is a JSON file for demo purposes (
.oauth-store.json). Replace with Postgres / Redis / your real datastore for production. application_token— Bitrix24 sends this with both install and uninstall events. The recipe stores it but does not verify it on uninstall — in production, compare the value before removing credentials to protect against spoofed uninstall calls.setCallbackRefreshAuthis wired at everyclientForMember()call. This ensures the storage always has the latest token pair after the SDK auto-refreshes a 401.- Always return 200 to the install / uninstall endpoints. Non-2xx triggers Bitrix24 retries for 24 hours.
- For multiple workers serving the same portal, use a transactional store (Postgres
ON CONFLICT UPDATE) so concurrent refreshes don't lose tokens. - For the wider per-portal multi-tenant pattern (cache strategy, lifecycle, revoked installs), this recipe is the foundation — wrap
clientForMemberin an LRU cache keyed bymemberIdfor hot portals.