# Bitrix24

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 40 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to Bitrix24 using a Firebase Cloud Function to proxy the incoming webhook URL, then call Bitrix24's dotted REST methods (crm.deal.add, crm.lead.list) from FlutterFlow API Calls. The webhook code is a permanent credential that must never ship in the app — the Cloud Function keeps it hidden while exposing a safe endpoint for your mobile app to push deals and leads into your sales pipeline.

## Push leads and deals from your FlutterFlow app into Bitrix24's sales pipeline

Bitrix24's REST API works differently from most CRMs. Rather than a standard Authorization header with an API key, its simplest authentication model uses an 'incoming webhook' — a permanent URL that embeds a secret code directly in the path: `https://yourportal.bitrix24.com/rest/{userId}/{webhookCode}/`. Any HTTP request to that URL is authorized as that user. This makes setup fast, but it also means the webhook URL itself IS the credential — anyone who has it can write to your CRM.

For FlutterFlow, this presents a clear security challenge. FlutterFlow compiles your app to Flutter code that runs on the user's device. If you put the full webhook URL into a FlutterFlow API Call, it gets compiled into the app binary and can be extracted by decompiling the APK or IPA. The solution is a backend proxy — a Firebase Cloud Function that stores the webhook URL server-side and exposes a safe, limited endpoint that your FlutterFlow app can call. FlutterFlow never sees the actual Bitrix24 webhook code.

Bitrix24 REST methods also have an unusual naming convention: they use dotted names as URL path segments, like `crm.deal.add`, `crm.lead.list`, or `tasks.task.get`. Parameters use bracket notation (`fields[TITLE]`, `fields[OPPORTUNITY]`) rather than nested JSON keys. List results come back under `$.result` with a `next` offset for pagination. Understanding these conventions upfront saves significant debugging time. Note that REST API access requires a commercial Bitrix24 plan — the free tier removed REST/webhook access in 2021. Current plan pricing starts at approximately $61/month for 5 users; verify current pricing on Bitrix24's site.

## Before you start

- A Bitrix24 account on a commercial plan (Basic or higher — REST/webhooks require a paid plan since 2021)
- A FlutterFlow project open in the browser
- A Firebase project with Cloud Functions enabled (Blaze pay-as-you-go plan required for outbound HTTP calls from Cloud Functions)
- Basic familiarity with the FlutterFlow API Calls panel
- Node.js 18+ installed locally to deploy the Firebase Cloud Function (one-time setup)

## Step-by-step guide

### 1. Create an incoming webhook in Bitrix24

Log in to your Bitrix24 portal. In the left navigation, look for 'Developer resources' — this is typically found under the main menu or in the portal settings area. If you don't see it immediately, search for 'webhooks' in the Bitrix24 search bar. Navigate to Developer resources → Other → Inbound webhook. Click 'Add' to create a new webhook.

On the webhook creation page, you need to select which scopes (permissions) the webhook will have. For CRM access, enable the 'CRM' scope. If you also need tasks, enable 'Tasks'. Keep the scope as narrow as your use case requires — a webhook with only CRM scope cannot accidentally modify tasks or other data if the credential is ever compromised.

After saving, Bitrix24 shows you the webhook URL in the format `https://yourportal.bitrix24.com/rest/{userId}/{webhookCode}/`. This is the sensitive credential. Copy the full URL and also note the code portion separately (the alphanumeric string after the user ID) — you'll need both in the next step. Do not paste this URL into FlutterFlow or source code. Close or save this page — you'll reference it only when configuring the Cloud Function.

Note that on free Bitrix24 accounts, creating this webhook will either fail or produce errors when called — REST API access requires a commercial plan. If you're evaluating the integration, sign up for a 30-day free trial of a commercial plan to test the full flow.

**Expected result:** You have a Bitrix24 incoming webhook URL in the format `https://yourportal.bitrix24.com/rest/{userId}/{code}/`. The webhook is active and has CRM scope enabled. You have the full URL saved securely.

### 2. Deploy a Firebase Cloud Function as a secure proxy

The Firebase Cloud Function is the heart of this integration's security model. It stores the Bitrix24 webhook URL as a server-side environment variable, never exposing it to FlutterFlow. When FlutterFlow calls the Cloud Function with a Bitrix24 method name and parameters, the function appends the webhook code and forwards the request to Bitrix24, then returns the response to FlutterFlow.

In your Firebase project, make sure you're on the Blaze plan (required for outbound HTTP calls from Cloud Functions). Create or open your `functions/index.js` file. The function should accept a `method` parameter (e.g. `crm.deal.add`) and a `fields` parameter (the payload), then construct the full Bitrix24 URL and POST to it.

Store the webhook URL using Firebase environment config: run `firebase functions:config:set bitrix.webhook_url=https://yourportal.bitrix24.com/rest/{userId}/{code}/` from your local terminal. The function reads this at runtime via `functions.config().bitrix.webhook_url`. Never hardcode the URL in the function source.

After writing the function, deploy it with `firebase deploy --only functions`. Firebase provides an HTTPS URL for the function — this is the only URL you'll put into FlutterFlow. Test it from Postman by sending a POST with `{"method": "crm.deal.list", "fields": {}}` and verifying you get back a Bitrix24 `$.result` array. If the function returns data, your webhook is working correctly.

```
// functions/index.js
const functions = require('firebase-functions');
const axios = require('axios');

// Set via: firebase functions:config:set bitrix.webhook_url=https://yourportal.bitrix24.com/rest/1/abc123/
exports.bitrix24Proxy = functions.https.onRequest(async (req, res) => {
  // CORS for FlutterFlow web builds
  res.set('Access-Control-Allow-Origin', '*');
  if (req.method === 'OPTIONS') {
    res.set('Access-Control-Allow-Methods', 'POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type');
    return res.status(204).send('');
  }

  const webhookUrl = functions.config().bitrix.webhook_url;
  if (!webhookUrl) {
    return res.status(500).json({ error: 'Webhook URL not configured' });
  }

  const { method, fields = {}, start = 0 } = req.body;
  if (!method) {
    return res.status(400).json({ error: 'method is required' });
  }

  // Build the Bitrix24 method URL
  // Method names are path segments: crm.deal.add → .../crm.deal.add.json
  const bitrixUrl = `${webhookUrl.replace(/\/$/, '')}/${method}.json`;

  try {
    const payload = { ...fields, start };
    const response = await axios.post(bitrixUrl, payload, {
      headers: { 'Content-Type': 'application/json' },
    });

    return res.status(200).json(response.data);
  } catch (err) {
    console.error('Bitrix24 proxy error:', err.response?.data || err.message);
    return res.status(err.response?.status || 500).json({
      error: 'Bitrix24 request failed',
      details: err.response?.data || err.message,
    });
  }
});
```

**Expected result:** Your Firebase Cloud Function is deployed and responds to POST requests. A test POST with `{"method": "crm.deal.list"}` returns a Bitrix24 result object with `{"result": [...], "total": N}`. You have the Cloud Function HTTPS URL ready.

### 3. Create the FlutterFlow API Group pointing at your Cloud Function

In FlutterFlow, open your project and click 'API Calls' in the left navigation sidebar. Click '+ Add' and select 'Create API Group'. Name it 'Bitrix24' and set the base URL to your Firebase Cloud Function HTTPS URL — for example, `https://us-central1-yourproject.cloudfunctions.net/bitrix24Proxy`. This is NOT the Bitrix24 portal URL — it's your own Firebase function. This is the critical difference: FlutterFlow only ever talks to your Cloud Function, never directly to Bitrix24.

Add a shared header to the API Group: `Content-Type: application/json`. No Authorization header is needed here — the Cloud Function handles authentication with Bitrix24 internally.

Now create the first API Call inside the group. Click '+ Add' inside the Bitrix24 group and create a call named 'List Deals'. Set the method to POST (the Cloud Function accepts POST for all calls) and leave the path empty (the base URL is the full function URL). Go to the Variables tab and add one variable: `method` (String, default value: `crm.deal.list`). Add a second optional variable `start` (Integer, default: 0) for pagination.

Switch to the Body tab, select JSON, and enter: `{"method": "{{method}}", "start": {{start}}}`. In the Response & Test tab, set the method variable to `crm.deal.list` and click Test. If the Cloud Function is deployed correctly, you should see a response with a `result` array. Click 'Generate JSON Paths' and find the path `$.result` which points to the array of deals.

Repeat for a second API Call named 'Create Deal' — same method (POST), same base URL, with variables: `method` (String, default: `crm.deal.add`), `title` (String), `opportunity` (String). The body for this call uses Bitrix24's bracket-notation field names embedded in the JSON body that the Cloud Function will forward.

```
{
  "List Deals body": {
    "method": "crm.deal.list",
    "start": 0
  },
  "Create Deal body": {
    "method": "crm.deal.add",
    "fields": {
      "TITLE": "{{title}}",
      "OPPORTUNITY": "{{opportunity}}",
      "CURRENCY_ID": "USD",
      "STAGE_ID": "NEW"
    }
  },
  "List Leads body": {
    "method": "crm.lead.list",
    "start": 0
  },
  "Create Lead body": {
    "method": "crm.lead.add",
    "fields": {
      "TITLE": "{{title}}",
      "NAME": "{{firstName}}",
      "LAST_NAME": "{{lastName}}",
      "EMAIL": [{"VALUE": "{{email}}", "VALUE_TYPE": "WORK"}]
    }
  }
}
```

**Expected result:** The Bitrix24 API Group appears in FlutterFlow with 'List Deals' and 'Create Deal' calls. Clicking Test on 'List Deals' returns your actual Bitrix24 deal data. JSON paths like `$.result[0].TITLE` auto-generate in the Response & Test tab.

### 4. Build a deals ListView bound to crm.deal.list results

Now wire the Bitrix24 data to a FlutterFlow UI component. On your target screen, add a ListView widget from the widget panel. In the right panel under 'Backend Query', click 'Add Query', select 'API Call', choose your Bitrix24 group, and select the 'List Deals' call. Set the `method` variable to the literal string `crm.deal.list` and `start` to 0.

Map the response to the ListView using the JSON path `$.result` — this is the array of deal objects that Bitrix24 returns. Inside the ListView's list item template, add Text widgets for the deal fields you want to display. Use 'Set from Variable' → 'Current Item' to bind each Text widget to a JSON path within the deal object. Common fields to display: `TITLE` (deal name), `OPPORTUNITY` (deal value), `STAGE_ID` (current stage), `DATE_CREATE`.

For the deal value, note that Bitrix24 returns `OPPORTUNITY` as a string number (e.g., `"5000.00"`). You may need a Custom Function to format it as currency — create one in Custom Code → + Add → Function with a simple Dart function that parses the string and formats it with a currency symbol.

To add pagination, create a page-level variable (local state) named `currentOffset` (Integer, default 0). Add a 'Load More' Button at the bottom of the list. In its action, increment `currentOffset` by the result count (Bitrix24 returns 50 results per page by default), then re-query the API with the new `start` value. Bitrix24's `next` field in the response tells you when there are more pages — parse `$.next` and show the button conditionally only when it's present.

**Expected result:** The ListView displays your Bitrix24 deals with title, value, and stage. A 'Load More' button at the bottom correctly fetches the next page of deals using the `start` offset. The list refreshes when the screen loads.

### 5. Build a create-deal form and wire the crm.deal.add action

Add a new screen or a bottom sheet to your app for creating deals. Place TextFormField widgets for the required deal fields: Deal Title (maps to Bitrix24's `TITLE`), Deal Value (maps to `OPPORTUNITY`), and optionally Contact Name and Stage. If you want a stage picker, use a Dropdown widget with values matching Bitrix24's `STAGE_ID` constants (e.g., `NEW`, `PREPARATION`, `PROPOSAL`, `NEGOTIATION`, `WON`).

Add a 'Save Deal' button. In the Action Flow Editor for this button, add an API Request action targeting the 'Create Deal' API Call. Map each variable:

- `method` → literal string `crm.deal.add`
- The Cloud Function will pass the `fields` object to Bitrix24 with bracket-notation parameters

For the Cloud Function to correctly forward the deal fields, the FlutterFlow API Call body should send the fields as a JSON object under a `fields` key (as shown in the previous step's code example). The Cloud Function then forwards this as-is to Bitrix24.

After the API Request action, add a conditional: if the response status code is 200 and `$.result` is not empty, the deal was created. The `result` field in a successful `crm.deal.add` response is the new deal's numeric ID (e.g., `{"result": 147}`). Show a success snack bar with the message 'Deal created successfully'. Then navigate back or clear the form using a Reset Form action.

For error handling, check if `$.error` is present in the response. Common errors include `ACCESS_DENIED` (webhook scope issue), `INVALID_REQUEST` (missing required fields), or `QUERY_LIMIT_EXCEEDED` (rate limit hit). Show these as user-friendly snack bar messages rather than raw error strings.

**Expected result:** Filling in the deal form and tapping 'Save Deal' creates a new deal in Bitrix24. The success snack bar appears and the new deal is visible in Bitrix24's CRM pipeline within seconds. The deals ListView refreshes to show the new entry when navigating back.

### 6. Handle rate limits and error responses

Bitrix24 uses a leaky-bucket rate limiting algorithm. The sustainable rate is approximately 2 requests per second, with short bursts allowed above this. When the limit is exceeded, Bitrix24 returns a `QUERY_LIMIT_EXCEEDED` error in the response body. This appears inside the `error` field of the JSON response, not as an HTTP status code — the HTTP status is still 200, but the body contains an error object.

In your Action Flow, after each API Call action, add a conditional check on the API response. The condition should check whether `$.error` equals `QUERY_LIMIT_EXCEEDED`. If it does, show a snack bar asking the user to wait a moment and try again. For list-refresh scenarios (like a pull-to-refresh), add a debounce using a Timer action or a local page state `isLoading` boolean that prevents re-calling the API while a request is in flight.

For the 'Load More' pagination button, add a delay between user taps by toggling a `loadingMore` boolean page state: set it to true before the API call, set it to false after. Conditionally disable the button while `loadingMore` is true.

Note an important Bitrix24 behavior: if you're on a plan with a limit lower than your API call frequency, you may hit sustained rate limits during ListView refreshes on multiple devices simultaneously. The leaky-bucket resets over time, so the solution is to reduce refresh frequency — don't auto-refresh the deals list on every screen focus. Instead, add a manual pull-to-refresh gesture and refresh only when the user explicitly requests it.

If you're building a feature that genuinely needs high-frequency Bitrix24 reads, consider implementing a caching layer: store the deals array in App State and refresh it once when the app launches, then use local App State data for subsequent list renders rather than re-hitting the API.

For production apps, consider having RapidDev review your API call frequency — free scoping call at rapidevelopers.com/contact.

**Expected result:** Your app gracefully handles rate limit errors with a user-friendly message. List refreshes are user-initiated (not automatic), keeping API call frequency well within Bitrix24's leaky-bucket limits. Error responses are surfaced as readable messages rather than technical strings.

## Best practices

- Never put the Bitrix24 incoming webhook URL — or any portion of it including the webhook code — in a FlutterFlow API Call, App Constant, or Dart code. Always proxy through a Firebase Cloud Function.
- Store the webhook URL in Firebase environment config (`firebase functions:config:set`) or Google Secret Manager rather than hardcoding it in the function source file.
- Use a single generic proxy Cloud Function that accepts a `method` parameter rather than one function per Bitrix24 method — this reduces maintenance overhead and deployment complexity.
- Use uppercase field names in Bitrix24 API calls (TITLE, OPPORTUNITY, STAGE_ID, CURRENCY_ID) — Bitrix24's REST API is case-sensitive and lowercase or camelCase field names silently fail or return unexpected errors.
- Cache list results (deals, leads) in App State and refresh only when the user explicitly pulls to refresh — do not auto-refresh on every screen navigation, as this quickly exhausts Bitrix24's leaky-bucket rate limit.
- Parse `$.result` for list responses and `$.result` (integer) for create responses — Bitrix24 returns the new entity ID as `result` on successful creation, and an array under `result` for list methods.
- Add a `QUERY_LIMIT_EXCEEDED` check in your Action Flow after every API Call, since Bitrix24 rate limit errors come back as HTTP 200 with an error body rather than a 4xx status code.
- Verify your Bitrix24 plan supports REST API access before building the integration — free plans have no REST access, and webhook creation may appear to succeed but calls will fail at runtime.

## Use cases

### Field sales app that captures deals from customer visits

A FlutterFlow mobile app for a sales team lets reps fill in deal information during or after a customer visit — company name, deal value, stage, and notes. Tapping 'Save Deal' calls the Firebase Cloud Function which posts to `crm.deal.add` in Bitrix24. The deal appears immediately in the pipeline on the desktop CRM. The app also shows the rep's current open deals by calling `crm.deal.list` filtered by the rep's assignedId, displayed in a ListView.

Prompt example:

```
Build a deal capture form with fields for company name, deal value, and stage. On submit, call a Firebase Cloud Function that posts to Bitrix24 crm.deal.add and returns the new deal ID.
```

### Event lead scanner that creates Bitrix24 contacts

A FlutterFlow app used at trade shows and events lets staff scan business cards or enter contact details manually. Submitting the form calls `crm.contact.add` via the Cloud Function proxy, creating a new contact in Bitrix24 with source tagged as 'Event'. A follow-up task is automatically created using `tasks.task.add` and assigned to the sales owner. All captured leads feed directly into the CRM pipeline without manual entry back at the office.

Prompt example:

```
Create a contact capture form with name, email, company, and phone fields. On submit, call Bitrix24's crm.contact.add via my Cloud Function and also create a follow-up task with tasks.task.add.
```

### Manager dashboard app showing pipeline at a glance

A FlutterFlow read-only dashboard app for sales managers shows today's deals by stage using a kanban-style Column layout. The app fetches deals with `crm.deal.list` filtered by date and status, displays them in grouped ListViews, and shows a summary card with total pipeline value. The manager can tap any deal to see its full details retrieved via `crm.deal.get`. No data entry — pure read access to the CRM from a mobile-friendly interface.

Prompt example:

```
Build a pipeline dashboard that fetches open deals from Bitrix24 via a Cloud Function, groups them by stage, and displays them in a scrollable list with deal name, value, and responsible person.
```

## Troubleshooting

### ACCESS_DENIED error from Bitrix24 when calling crm.deal.list or crm.deal.add

Cause: The Bitrix24 incoming webhook was created without the CRM scope, or the webhook belongs to a user without CRM permissions in the portal.

Solution: Go to Bitrix24 → Developer resources → Other → Inbound webhook and check the webhook's scope settings. Enable the 'CRM' scope and save. If the webhook was created by a user with limited CRM permissions, create a new webhook under an admin user account. Update the webhook URL in your Firebase Cloud Function config (`firebase functions:config:set bitrix.webhook_url=...`) and redeploy.

### 400 Bad Request when calling crm.deal.add — deal is not created

Cause: Bitrix24 field params must use the correct field names (uppercase: TITLE, OPPORTUNITY) and the TITLE field is mandatory. Sending empty or incorrectly named fields triggers a 400.

Solution: Ensure your Cloud Function forwards the fields object with uppercase Bitrix24 field names (`TITLE`, `OPPORTUNITY`, `CURRENCY_ID`) rather than lowercase or camelCase. Add a validator to the FlutterFlow TextFormField for deal title so it cannot be empty before the API Call fires. Test the exact request payload in Postman against your Cloud Function to verify the field structure before debugging in FlutterFlow.

### QUERY_LIMIT_EXCEEDED in API response (HTTP 200 but error body)

Cause: The Bitrix24 leaky-bucket rate limiter was exceeded. Sustained rates above ~2 requests/second trigger this. A common cause is a ListView with auto-refresh on every screen focus or navigation.

Solution: Remove any automatic refresh-on-focus logic from your ListViews. Replace with user-triggered pull-to-refresh. Add a `isLoading` page state boolean to prevent concurrent API calls. Cache the deals list in App State and only refresh when the user explicitly requests it. Check if multiple screens are all independently polling the same Bitrix24 method.

### XMLHttpRequest error when testing in FlutterFlow Run mode (web preview)

Cause: Browser-enforced CORS blocks direct calls. However, since FlutterFlow calls your Firebase Cloud Function (not Bitrix24 directly), this error means your Cloud Function is not setting CORS headers correctly.

Solution: Ensure your Cloud Function includes CORS response headers: `res.set('Access-Control-Allow-Origin', '*')` for the preflight OPTIONS request AND for actual responses. The Cloud Function code example in Step 2 includes these headers. If you see this error, check that you deployed the latest version of the function after adding the CORS headers. Run `firebase deploy --only functions` again and verify the function URL is accessible from your browser.

### Free Bitrix24 account — webhook returns 'REST API is not available on free plan' error

Cause: Bitrix24 removed REST API and webhook access from free plans in 2021. Free-tier portals cannot use incoming webhooks or the REST API at all.

Solution: Upgrade to a commercial Bitrix24 plan (Basic or higher). The Basic plan is the minimum tier that includes REST API access. If you're evaluating the integration, use Bitrix24's 30-day commercial trial to test the full setup before committing to a subscription.

## Frequently asked questions

### Does Bitrix24's REST API work on the free plan?

No. Bitrix24 removed REST API and incoming webhook access from free plans in 2021. You need at minimum the Basic commercial plan to use the REST API. If you see your webhook URL created successfully on a free account but calls return access errors, this is the reason. Bitrix24 offers a 30-day free trial of commercial plans, which is a good way to test the full integration before subscribing.

### Why can't I just put the webhook URL directly in a FlutterFlow API Call?

The Bitrix24 incoming webhook URL contains a permanent secret code as part of the URL path — it IS the credential. If you put this URL in a FlutterFlow API Call, it gets compiled into the Flutter app binary. Anyone who downloads your app and runs a decompiler (a free, widely-available tool) can extract this URL and use it to read or write CRM data as your user, indefinitely. There is no expiry. The Firebase Cloud Function proxy keeps the URL server-side where it cannot be extracted from the app.

### Why do Bitrix24 API calls use POST even for read operations like crm.deal.list?

Bitrix24's REST API accepts both GET and POST for most methods. When using GET, parameters go in the query string with bracket notation (`fields[TITLE]=example`). When using POST with a JSON body, parameters go in the JSON object. The Cloud Function proxy in this tutorial uses POST with JSON because it's easier to construct complex nested payloads in JSON than to URL-encode bracket notation in query strings. Either approach works — POST with JSON body is the more reliable choice for complex filter parameters.

### How do I filter deals by stage or assignee when using crm.deal.list?

Add a `filter` key to the request body with Bitrix24 field names as keys. For example, to list only deals in the NEW stage assigned to a specific user: `{"method": "crm.deal.list", "filter": {"STAGE_ID": "NEW", "ASSIGNED_BY_ID": "5"}}`. Your Cloud Function should pass this filter object through to Bitrix24 alongside the method. In FlutterFlow, add filter variables to your API Call and include them in the request body. Check Bitrix24's CRM field documentation for the exact field names used in filters.

### Can my FlutterFlow app receive real-time updates from Bitrix24 (webhooks)?

Not directly. Bitrix24 can send outbound webhooks (event notifications) when CRM records change, but these webhooks fire to a URL — and FlutterFlow apps running on a mobile device don't have a public URL to receive them. To get real-time updates, you would need your Firebase Cloud Function to receive Bitrix24's outbound webhook, write the data to Firestore, and have FlutterFlow listen to Firestore with a real-time listener. This is a more complex architecture but is the correct pattern for event-driven updates.

### What's the difference between Bitrix24's incoming webhooks and OAuth apps?

Incoming webhooks are the simpler option: a permanent URL tied to a specific Bitrix24 user, with no token expiry. They work for internal tools and integrations where you control both ends. OAuth apps are required for marketplace applications or when you need to act on behalf of multiple Bitrix24 users dynamically — the user goes through an OAuth consent flow, and you get per-user access tokens that need refreshing. For a FlutterFlow app your team uses internally (a field sales app, a manager dashboard), incoming webhooks with a Cloud Function proxy are simpler and sufficient.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/bitrix24
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/bitrix24
