1C-Bitrix Integration with Honest Sign (Chestny Znak)
Honest Sign is Russia's mandatory product labeling system. Since 2020–2024, labeling requirements have expanded to footwear, clothing, dairy products, tobacco, water, perfumery, dietary supplements, antiseptics, and several other categories. Any online store selling labeled goods is required to retire Data Matrix codes from circulation at the point of sale. Without integration with Honest Sign, the business faces administrative fines and potential blocking.
How labeling works in e-commerce
When goods arrive at the warehouse, labeling codes are registered in Honest Sign (entered into circulation). When sold, they are retired from circulation (recording the fact of sale to the end consumer). Retirement happens either through a fiscal receipt via an OFD (fiscal data operator) or directly through the Honest Sign API.
For an online store, the flow is:
- Goods arrive at the warehouse → codes are scanned → entered into circulation via the Honest Sign API
- Order is placed → at dispatch/handover, a receipt is generated via OFD containing the labeling code → OFD automatically retires the code from circulation
If OFD is not used (click-and-collect without a cash register receipt, courier delivery with an electronic receipt) — codes are retired directly via the Honest Sign API.
Honest Sign API (GIS MT)
The API is available at https://ismp.crpt.ru/api/v3/. Authentication uses a JWT token obtained with a qualified electronic signature (QES):
class ChestnyZnakClient
{
private string $baseUrl = 'https://ismp.crpt.ru/api/v3';
private string $token;
public function __construct(private string $inn, private string $certSerial)
{
$this->token = $this->authenticate();
}
private function authenticate(): string
{
// Step 1: request data to sign
$authData = $this->httpGet('/auth/cert/key');
$uuid = $authData['uuid'];
$data = $authData['data'];
// Step 2: sign data with QES
$signature = $this->signWithCert($data, $this->certSerial);
// Step 3: obtain token
$tokenResponse = $this->httpPost('/auth/cert/', [
'uuid' => $uuid,
'data' => $data,
'signature' => base64_encode($signature),
]);
return $tokenResponse['token'];
}
public function withdrawCodes(array $codes, string $docType = 'SELL'): string
{
$documents = [];
foreach ($codes as $code) {
$documents[] = [
'certificateDocument' => '',
'certificateDocumentDate' => '',
'certificateDocumentNumber' => '',
'tnvedCode' => '',
'uitCode' => $code,
'uituCode' => '',
];
}
$response = $this->httpPost('/lk/documents/create', [
'document_format' => 'MANUAL',
'product_document' => base64_encode(json_encode([
'participantInn' => $this->inn,
'documentFormat' => 'MANUAL',
'type' => $docType, // SELL, RETURN, LOSS
'transferDate' => date('Y-m-d'),
'products' => $documents,
])),
'signature' => $this->signPayload(...),
'type' => $docType,
]);
return $response['documentId'];
}
}
Storing labeling codes in 1C-Bitrix
Data Matrix codes are stored in a custom table linked to order line items:
class MarkingCodeTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string { return 'local_marking_codes'; }
public static function getMap(): array
{
return [
new \Bitrix\Main\ORM\Fields\IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
new \Bitrix\Main\ORM\Fields\IntegerField('PRODUCT_ID'), // catalog product ID
new \Bitrix\Main\ORM\Fields\StringField('CODE'), // Data Matrix code
new \Bitrix\Main\ORM\Fields\StringField('STATUS'), // 'in_stock', 'in_order', 'sold'
new \Bitrix\Main\ORM\Fields\IntegerField('ORDER_ID'), // null until assigned to an order
new \Bitrix\Main\ORM\Fields\IntegerField('BASKET_ITEM_ID'),
new \Bitrix\Main\ORM\Fields\DatetimeField('WITHDRAWAL_DATE'),
];
}
}
When a product is added to an order, a specific labeling code is reserved for that line item. If the order is cancelled, the code is released back to in_stock status.
Retiring codes at dispatch
// Order shipment event handler
public function handleOrderShipped(\Bitrix\Sale\Order $order): void
{
$markedItems = $this->getMarkedItemsFromOrder($order);
if (empty($markedItems)) return;
$codes = array_column($markedItems, 'CODE');
$chestnyZnak = new ChestnyZnakClient(CZ_INN, CZ_CERT_SERIAL);
$documentId = $chestnyZnak->withdrawCodes($codes, 'SELL');
// Update code statuses
foreach ($markedItems as $item) {
MarkingCodeTable::update($item['ID'], [
'STATUS' => 'sold',
'ORDER_ID' => $order->getId(),
'WITHDRAWAL_DATE' => new \Bitrix\Main\Type\DateTime(),
'CZ_DOCUMENT_ID' => $documentId,
]);
}
}
Case study: clothing store with mandatory labeling
A women's clothing store processing approximately 1,500 orders per month. Products are labeled light-industry garments. The requirement: automatically retire codes from circulation on every dispatch, and return codes to circulation on returns.
Implementation details:
Labeling codes arrive from suppliers in Excel files. An importer was developed: Excel is uploaded via a handler in the 1C-Bitrix admin area → parsed → codes are added to local_marking_codes with status in_stock.
During warehouse picking, a storekeeper scans the code on each item. A mobile application (React Native, WebView with 1C-Bitrix) sends the scan results to the server, which binds specific codes to order line items.
On dispatch — codes are automatically retired from circulation. On return — POST /api/v3/lk/documents/create with type RETURN, and the code reverts to in_stock status.
| Metric | Before | After |
|---|---|---|
| Manual code retirement | 1.5–2 hours/day | Automatic |
| Retirement errors (wrong code) | ~3%/month | < 0.3% |
| Returns to Honest Sign | Skipped | Automatic on return processing |
Code verification before sale
Before dispatch, the code status in Honest Sign is verified (to confirm it has not already been retired through another channel):
$codeInfo = $chestnyZnak->httpGet("/facade/identificationtools?uitCode={$code}");
if ($codeInfo['status'] !== 'IN_CIRCULATION') {
throw new \Exception("Code {$code} is not in circulation: {$codeInfo['status']}");
}
Scope of work
- Registration with Honest Sign, API connection, QES certificate
- PHP client development: authentication, code retirement, returns
- Labeling code storage table in 1C-Bitrix
- Code import from suppliers
- Warehouse inventory integration: code reservation on order
- Automatic retirement on dispatch, reinstatement on order return
- Monitoring: Honest Sign document statuses, retirement errors
Timeline: basic integration (sale retirement, returns) — 4–6 weeks. With code import, warehouse management, and mobile scanning — 10–16 weeks.







