---
title: "OAuth install handshake"
description: "Express server that handles ONAPPINSTALL / ONAPPUPDATE / ONAPPUNINSTALL events for a Bitrix24 marketplace app, persists tokens per portal, and builds a B24OAuth client on demand with a refresh callback wired to storage."
canonical_url: "https://bitrix24.github.io/b24jssdk/docs/examples/oauth-install"
last_updated: "2026-06-02"
---
# OAuth install handshake

> Express server that handles ONAPPINSTALL / ONAPPUPDATE / ONAPPUNINSTALL events for a Bitrix24 marketplace app, persists tokens per portal, and builds a B24OAuth client on demand with a refresh callback wired to storage.

## 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:

1. Receives the install event and **persists the OAuth tokens per portal** (`member_id` as the key).
2. Exposes `clientForMember(memberId)` — builds a `B24OAuth` instance from stored tokens, attaches a refresh callback so the next refreshed pair is written back to storage automatically.
3. Removes credentials on uninstall.
4. 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

```bash
pnpm add express
```

## Environment

```bash
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

```bash
npx tsx 12-oauth-install.ts
```

Then trigger the handshake by installing the app on a Bitrix24 portal. Verify with:

```bash
curl https://your-server.example.com/portal/<memberId>/profile
```

## Source

[`skills/b24jssdk-recipes/examples/12-oauth-install.ts`](https://github.com/bitrix24/b24jssdk/blob/main/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.
- **setCallbackRefreshAuth** is wired at every `clientForMember()` 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 `clientForMember` in an LRU cache keyed by `memberId` for hot portals.

## Sitemap

See the full [sitemap](/b24jssdk/sitemap.md) for all pages.
