How to Connect a Cash Register to Bitrix24
Scope:
cashboxWho can execute methods: administrator
If you are developing integrations for Bitrix24 using AI tools (Codex, Claude Code, Cursor), connect to the MCP server so that the assistant can utilize the official REST documentation.
In Bitrix24, you can connect an external cash register and automatically print receipts. When a customer pays for an order, Bitrix24 sends the receipt data to the specified URL. An external service then generates and registers the fiscal receipt.
As a result of the scenario, a REST cash register will appear in the Sales Center, and the external service will be able to accept receipt-print requests and return the print status.
The scenario consists of three steps.
- Add a cash register handler using the sale.cashbox.handler.add method
- Create a cash register and link it to the handler using the sale.cashbox.add method
- Pass the receipt print result using the sale.cashbox.check.apply method if the status must be retained manually
Before You Start
Prepare the values used in the examples:
- an inbound webhook or an OAuth token of a user with administrator permissions
- a public HTTPS address of the receipt print page
PRINT_URL - a public HTTPS address of the receipt status page
CHECK_URL - a unique cash register handler code, for example
my_rest_cashbox - the authorization data of the external cash register that the administrator will enter in the cash register settings
Do not place login credentials, passwords, or access tokens in public code. Pass secrets through environment variables or secure application storage.
1. Add the Cash Register Handler
Register a handler using sale.cashbox.handler.add. Pass the handler configurations and the addresses to which the account sends requests for printing and checking the receipt status to the method.
-
CODE— the unique code of the handler. We will specifymy_rest_cashbox. -
NAME— the name of the handler, for example, My REST cash register. -
SORT— a number that determines the position of the handler in the list. -
SETTINGS— the handler settings object.-
PRINT_URL— the address to which Bitrix24 sends the receipt data for printing. We will specifyhttps://example.com/rest_print.php. -
CHECK_URL— the address used to check the receipt status. We will passhttps://example.com/rest_check.php. -
CONFIG— the fields that need to be created for the handler. An Administrator fills in these fields when configuring the cash register. We will create three blocks:AUTH— authorization via login and password,COMPANY— company details,INTERACTION— cash register operation mode.
-
How to Use Examples in Documentation
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/'
const response = await $b24.actions.v2.call.make({
method: 'sale.cashbox.handler.add',
params: {
CODE: 'my_rest_cashbox',
NAME: 'My REST cash register',
SORT: 100,
SETTINGS: {
PRINT_URL: 'https://example.com/rest_print.php',
CHECK_URL: 'https://example.com/rest_check.php',
CONFIG: {
AUTH: {
LABEL: 'Authorization',
ITEMS: {
LOGIN: {
TYPE: 'STRING',
REQUIRED: 'Y',
LABEL: 'Login'
},
PASSWORD: {
TYPE: 'STRING',
REQUIRED: 'Y',
LABEL: 'Password'
}
}
},
COMPANY: {
LABEL: 'Organization data',
ITEMS: {
INN: {
TYPE: 'STRING',
REQUIRED: 'Y',
LABEL: 'Tax ID of the organization'
}
}
},
INTERACTION: {
LABEL: 'Cash register interaction settings',
ITEMS: {
MODE: {
TYPE: 'ENUM',
REQUIRED: 'N',
LABEL: 'Cash register operating mode',
OPTIONS: {
ACTIVE: 'production',
TEST: 'test'
}
}
}
}
}
}
},
requestId: 'cashbox-handler-add'
})
if (response.isSuccess) {
console.dir(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
from b24pysdk import BitrixWebhook, Client
from b24pysdk.errors import BitrixAPIError
token = BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token="user_id/webhook_key",
)
client = Client(token)
try:
response = client.sale.cashbox.handler.add(
code="my_rest_cashbox",
name="My REST cash register",
sort=100,
settings={
"PRINT_URL": "https://example.com/rest_print.php",
"CHECK_URL": "https://example.com/rest_check.php",
"CONFIG": {
"AUTH": {
"LABEL": "Authorization",
"ITEMS": {
"LOGIN": {
"TYPE": "STRING",
"REQUIRED": "Y",
"LABEL": "Login",
},
"PASSWORD": {
"TYPE": "STRING",
"REQUIRED": "Y",
"LABEL": "Password",
},
},
},
"COMPANY": {
"LABEL": "Organization data",
"ITEMS": {
"INN": {
"TYPE": "STRING",
"REQUIRED": "Y",
"LABEL": "Tax ID of the organization",
}
},
},
"INTERACTION": {
"LABEL": "Cash register interaction settings",
"ITEMS": {
"MODE": {
"TYPE": "ENUM",
"REQUIRED": "N",
"LABEL": "Cash register operating mode",
"OPTIONS": {
"ACTIVE": "production",
"TEST": "test",
},
}
},
},
},
},
).response
print(response.result)
except BitrixAPIError as error:
print(error)
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('b24');
$log->pushHandler(new StreamHandler('php://stdout'));
$sb = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->initFromWebhook('https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/');
$result = $sb->getSaleScope()->cashboxHandler()->add(
'my_rest_cashbox',
'My REST cash register',
[
'PRINT_URL' => 'https://example.com/rest_print.php',
'CHECK_URL' => 'https://example.com/rest_check.php',
'CONFIG' => [
'AUTH' => [
'LABEL' => 'Authorization',
'ITEMS' => [
'LOGIN' => [
'TYPE' => 'STRING',
'REQUIRED' => 'Y',
'LABEL' => 'Login'
],
'PASSWORD' => [
'TYPE' => 'STRING',
'REQUIRED' => 'Y',
'LABEL' => 'Password'
],
]
],
'COMPANY' => [
'LABEL' => 'Organization data',
'ITEMS' => [
'INN' => [
'TYPE' => 'STRING',
'REQUIRED' => 'Y',
'LABEL' => 'Tax ID of the organization'
]
]
],
'INTERACTION' => [
'LABEL' => 'Cash register interaction settings',
'ITEMS' => [
'MODE' => [
'TYPE' => 'ENUM',
'REQUIRED' => 'N',
'LABEL' => 'Cash register operating mode',
'OPTIONS' => [
'ACTIVE' => 'production',
'TEST' => 'test'
]
]
]
]
]
],
100
);
echo '<PRE>';
print_r($result->getId());
echo '</PRE>';
If the handler is added successfully, the method returns its identifier. Store the result value: you will need it to find the handler in the list.
{
"result": 1
}
The handler can now be used to create cash registers in the Bitrix24 interface. One handler can serve multiple cash registers with different company details.

2. Configure the Cashbox
Add a cashbox using sale.cashbox.add. Pass the cashbox configurations and the value of the CONFIG parameter from the previous step to the method.
-
REST_CODE— handler code. Pass the valuemy_rest_cashbox, which you specified when adding a handler in the parameterCODE. -
NAME— cashbox name. Specify REST cash register. -
NUMBER_KKM— external cashbox identifier, for example,1. -
OFD— OFD handler code. Passbx_firstofd. See the list of possible values in the sale.cashbox.add method documentation. -
EMAIL— email address for notifications. Specifyowner@example.com. -
USE_OFFLINE— flag indicating whether the cashbox is used offline. Set the valueY. -
ACTIVE— cashbox activity. SpecifyY. -
SETTINGS— data for the fields created in theCONFIGparameter in the previous step. They must be filled out exactly as described during the handler registration:-
AUTH— login and password for authorization, -
COMPANY— organization Taxpayer ID, -
INTERACTION— operating mode, for example,ACTIVE.
-
const response = await $b24.actions.v2.call.make({
method: 'sale.cashbox.add',
params: {
REST_CODE: 'my_rest_cashbox',
NAME: 'REST cash register',
NUMBER_KKM: '1',
OFD: 'bx_firstofd',
EMAIL: 'owner@example.com',
USE_OFFLINE: 'Y',
ACTIVE: 'Y',
SETTINGS: {
AUTH: {
LOGIN: 'rest_login',
PASSWORD: 'rest_password'
},
COMPANY: {
INN: '1234567890'
},
INTERACTION: {
MODE: 'ACTIVE'
}
}
},
requestId: 'cashbox-add'
})
if (response.isSuccess) {
console.dir(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
try:
response = client.sale.cashbox.add(
rest_code="my_rest_cashbox",
name="REST cash register",
email="owner@example.com",
number_kkm="1",
ofd="bx_firstofd",
use_offline=True,
active=True,
settings={
"AUTH": {
"LOGIN": "rest_login",
"PASSWORD": "rest_password",
},
"COMPANY": {
"INN": "1234567890",
},
"INTERACTION": {
"MODE": "ACTIVE",
},
},
).response
print(response.result)
except BitrixAPIError as error:
print(error)
$result = $sb->getSaleScope()->cashbox()->add([
'REST_CODE' => 'my_rest_cashbox',
'NAME' => 'REST cash register',
'NUMBER_KKM' => '1',
'OFD' => 'bx_firstofd',
'EMAIL' => 'owner@example.com',
'USE_OFFLINE' => 'Y',
'ACTIVE' => 'Y',
'SETTINGS' => [
'AUTH' => [
'LOGIN' => 'rest_login',
'PASSWORD' => 'rest_password'
],
'COMPANY' => [
'INN' => '1234567890'
],
'INTERACTION' => [
'MODE' => 'ACTIVE'
]
]
]);
echo '<PRE>';
print_r($result->getId());
echo '</PRE>';
If the cashbox is added successfully, the method returns its identifier. Store the result value: you will need it to verify the cashbox in the list.
{
"result": 1
}
You can verify that the cashbox is connected to Bitrix24 in the Sales Center.

Printing Receipts
The cash register uses two addresses. The account sends printing data to PRINT_URL. Bitrix24 uses CHECK_URL to check whether the receipt has been printed and if there are any errors.
PRINT_URL Page
The PRINT_URL page is the address to which Bitrix24 sends the receipt data for printing. For the request structure, see the PRINT_URL Page section of the sale.cashbox.handler.add method.
Input data processing, document generation, and the return of the printing result occur at PRINT_URL.
Minimum PRINT_URL page logic:
- Receive receipt data from Bitrix24
- Verify that the request contains receipt data
- Pass the receipt to the external cash register
- Return
UUIDif the receipt is accepted for printing, or anERRORSarray if printing is impossible - Store the relation between
UUIDand the order or receipt in the external system so that theCHECK_URLpage can return the status
Example PRINT_URL handler in Node.js:
import express from 'express'
import { randomUUID } from 'crypto'
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
const checks = new Map()
app.post('/rest_print.php', async (req, res) => {
const payload = req.body
if (!payload || Object.keys(payload).length === 0) {
res.json({
ERRORS: ['Receipt data was not passed']
})
return
}
const uuid = randomUUID()
checks.set(uuid, {
status: 'WAIT',
createdAt: Date.now(),
payload
})
res.json({
UUID: uuid
})
})
In a production service, store UUID, the status, and the receipt data in a database rather than in process memory.
-
If printing fails, the JSON array looks like this:
{ "ERRORS": [ "Error message", "Error message", ... ] } -
If the receipt is sent to print, the array looks like this:
{ "UUID": "00112233-4455-6677-8899-aabbccddeeff" }
CHECK_URL Page
The CHECK_URL page is the address used by the account to check if the receipt is ready and if there are any errors.
A request to CHECK_URL is performed upon a manager's request or is triggered automatically after a certain amount of time following a successful receipt print. For the request structure, see the CHECK_URL Page section of the sale.cashbox.handler.add method.
A request to CHECK_URL returns data about the receipt, error data if a printing error occurred, or an "idle" status.
Minimum CHECK_URL page logic:
- Receive the receipt
UUID - Find the receipt in the external system
- If the receipt is still printing, return
STATUS: WAIT - If printing finished with an error, return
STATUS: ERRORand the error text - If the receipt is printed, return
STATUS: DONEand the fiscal details
Example CHECK_URL handler in Node.js:
app.post('/rest_check.php', async (req, res) => {
const uuid = req.body.UUID
if (!uuid || !checks.has(uuid)) {
res.json({
STATUS: 'ERROR',
ERROR: 'Receipt with this UUID was not found'
})
return
}
const check = checks.get(uuid)
if (check.status === 'WAIT') {
res.json({
STATUS: 'WAIT'
})
return
}
if (check.status === 'ERROR') {
res.json({
STATUS: 'ERROR',
ERROR: check.error
})
return
}
res.json({
STATUS: 'DONE',
UUID: uuid,
REG_NUMBER_KKT: '000111222333',
FISCAL_DOC_ATTR: '33445500',
FISCAL_DOC_NUMBER: 123,
FISCAL_RECEIPT_NUMBER: 10,
FN_NUMBER: '0011223344556677',
SHIFT_NUMBER: 12,
PRINT_END_TIME: Math.floor(Date.now() / 1000)
})
})
-
Data format in case of a printing error:
{ "STATUS": "ERROR", "ERROR": "Error message" } -
Data format if the receipt has not been printed:
{ "STATUS": "WAIT" } -
Data format upon successful receipt submission:
{ "STATUS": "DONE", "UUID": "00112233-4455-6677-8899-aabbccddeeff", "REG_NUMBER_KKT": "000111222333", "FISCAL_DOC_ATTR": "33445500", "FISCAL_DOC_NUMBER": 123, "FISCAL_RECEIPT_NUMBER": 10, "FN_NUMBER": "0011223344556677", "SHIFT_NUMBER": 12, "PRINT_END_TIME": 1609452000 }
The full list of fields matches the parameters of the sale.cashbox.check.apply method.
Data from CHECK_URL is retained in Bitrix24 and used to generate a link to the receipt.
Manually Submit Print Result
Receipt data can be submitted at any time using the sale.cashbox.check.apply method.
Prepare the fields for sale.cashbox.check.apply.
-
UUID— the unique ID of the receipt returned by the handler in the response toPRINT_URL. -
PRINT_END_TIME— the receipt printing End Time. -
REG_NUMBER_KKT— the cash register registration number. -
FISCAL_DOC_ATTR— the fiscal attribute of the document generated by the cash register. -
FISCAL_DOC_NUMBER— the fiscal document number. -
FISCAL_RECEIPT_NUMBER— the receipt number within the shift. -
FN_NUMBER— the fiscal storage device number. -
SHIFT_NUMBER— the shift number in which the receipt was recorded.
const response = await $b24.actions.v2.call.make({
method: 'sale.cashbox.check.apply',
params: {
UUID: '00112233-4455-6677-8899-aabbccddeeff',
PRINT_END_TIME: '1609459200',
REG_NUMBER_KKT: '000111222333',
FISCAL_DOC_ATTR: '33445500',
FISCAL_DOC_NUMBER: '123',
FISCAL_RECEIPT_NUMBER: '10',
FN_NUMBER: '0011223344556677',
SHIFT_NUMBER: '12'
},
requestId: 'cashbox-check-apply'
})
if (response.isSuccess) {
console.dir(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
try:
response = client.sale.cashbox.check.apply(
uuid="00112233-4455-6677-8899-aabbccddeeff",
print_end_time="1609459200",
reg_number_kkt="000111222333",
fiscal_doc_attr="33445500",
fiscal_doc_number="123",
fiscal_receipt_number="10",
fn_number="0011223344556677",
shift_number="12",
).response
print(response.result)
except BitrixAPIError as error:
print(error)
$result = $sb->getSaleScope()->cashbox()->checkApply([
'UUID' => '00112233-4455-6677-8899-aabbccddeeff',
'PRINT_END_TIME' => '1609459200',
'REG_NUMBER_KKT' => '000111222333',
'FISCAL_DOC_ATTR' => '33445500',
'FISCAL_DOC_NUMBER' => '123',
'FISCAL_RECEIPT_NUMBER' => '10',
'FN_NUMBER' => '0011223344556677',
'SHIFT_NUMBER' => '12'
]);
echo '<PRE>';
print_r($result->isSuccess());
echo '</PRE>';
If the receipt is successfully saved, the method returns true.
{
"result": true
}
Check the Result
After configuring the cash register, open the Sales Center and verify that the REST cash register is available in the list of cash registers. After a test payment, verify that the external service received the request to PRINT_URL, returned UUID, and that Bitrix24 received the status through CHECK_URL or the sale.cashbox.check.apply method.
Through REST, check the handler and the cash register using the sale.cashbox.handler.list and sale.cashbox.list methods.
const handlerResponse = await $b24.actions.v2.call.make({
method: 'sale.cashbox.handler.list',
params: {},
requestId: 'cashbox-handler-list',
})
const cashboxResponse = await $b24.actions.v2.call.make({
method: 'sale.cashbox.list',
params: {
SELECT: ['ID', 'NAME', 'ACTIVE', 'EMAIL'],
FILTER: { '=NAME': 'REST cash register' },
},
requestId: 'cashbox-list',
})
console.log(handlerResponse.getData().result)
console.log(cashboxResponse.getData().result)
handlers = token.call_method("sale.cashbox.handler.list", {})
cashboxes = token.call_method(
"sale.cashbox.list",
{
"SELECT": ["ID", "NAME", "ACTIVE", "EMAIL"],
"FILTER": {"=NAME": "REST cash register"},
},
)
print(handlers)
print(cashboxes)
$handlerResponse = $sb->core->call('sale.cashbox.handler.list', []);
$cashboxResponse = $sb->core->call('sale.cashbox.list', [
'SELECT' => ['ID', 'NAME', 'ACTIVE', 'EMAIL'],
'FILTER' => ['=NAME' => 'REST cash register'],
]);
print_r($handlerResponse->getResponseData()->getResult());
print_r($cashboxResponse->getResponseData()->getResult());
The scenario is successful if three results are confirmed:
- the
sale.cashbox.handler.addmethod returned the handler identifier - the
sale.cashbox.addmethod returned the cash register identifier - the
sale.cashbox.check.applymethod returnedtrueif the print result was submitted manually
Errors and Diagnostics
If a method returns an error, verify the request data and the external service state.
|
Error code or text |
Cause and action |
|
|
The method was called by a user without CRM administrator permissions to change settings |
|
|
A required field is missing or a field value failed validation. Check |
|
|
A handler with this |
|
|
The receipt with the specified |
|
|
An error occurred while adding the handler, creating the cash register, or saving the print result. Review |
|
Print error at |
Return the |
|
|
The receipt has not been printed yet. Repeat the status check after the receipt is processed by the external cash register |
What to Consider
- rerunning the example with the same
CODEmay fail because the handler code must be unique PRINT_URLandCHECK_URLmust be publicly available over HTTPS- store cash register settings data, such as login and password, on the application side and do not display it in the interface without masking