Integration of 1C-Bitrix with the Salsify PIM system

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Integration of 1C-Bitrix with Salsify PIM System

Salsify is an American SaaS PIM platform focused on large e-commerce and multi-channel sales. It is used primarily by brands and distributors managing thousands of SKUs with syndication to marketplaces (Amazon, Walmart, Target). If a Western brand has been migrated to Bitrix or a partnership requires using Salsify as a data source — integration is built through the Salsify REST API.

Salsify API: Key Endpoints

Salsify provides REST API with token authentication:

Base URL: https://app.salsify.com/api/v1/orgs/{org_id}/
Authorization: Bearer {api_key}

Key endpoints:

GET products                    — list of products with attributes
GET products/{product_id}       — specific product
GET products?filter[updated_at][gte]={timestamp}  — changed since date
GET property_groups             — attribute groups (similar to property sections)
GET assets?filter[product_id]={id}  — product media files
GET digital_assets/{id}/download    — download file

Salsify uses a flat attribute structure: each attribute is a name:value pair where name can be an arbitrary UUID or string. Before mapping, retrieve the attribute reference:

GET properties — returns all organization attributes with ID, name, data_type

Salsify Client

class SalsifyClient
{
    private string $baseUrl;
    private string $apiKey;

    public function __construct(string $orgId, string $apiKey)
    {
        $this->baseUrl = "https://app.salsify.com/api/v1/orgs/{$orgId}/";
        $this->apiKey  = $apiKey;
    }

    private function request(string $endpoint, array $params = []): array
    {
        $http = new \Bitrix\Main\Web\HttpClient();
        $http->setHeader('Authorization', 'Bearer ' . $this->apiKey);
        $http->setHeader('Content-Type', 'application/json');

        $url = $this->baseUrl . $endpoint;
        if ($params) {
            $url .= '?' . http_build_query($params);
        }

        $response = $http->get($url);
        return json_decode($response, true) ?? [];
    }

    public function getProducts(int $page = 1, int $perPage = 100, ?string $updatedAfter = null): array
    {
        $params = ['page' => $page, 'per_page' => $perPage];
        if ($updatedAfter) {
            $params['filter[updated_at][gte]'] = $updatedAfter;
        }
        return $this->request('products', $params);
    }

    public function getProperties(): array
    {
        return $this->request('properties');
    }

    public function downloadAsset(string $assetId): string
    {
        $http = new \Bitrix\Main\Web\HttpClient();
        $http->setHeader('Authorization', 'Bearer ' . $this->apiKey);
        return $http->get($this->baseUrl . 'digital_assets/' . $assetId . '/download');
    }
}

Salsify Attribute Mapping

Salsify attributes are identified by salsify:id (UUID) or by name. Mapping configuration:

// /local/config/salsify-mapping.php
// 'salsify_property_id_or_name' => ['target' => 'bitrix_field', 'type' => 'field|prop']
return [
    'Product Name'              => ['target' => 'NAME',          'type' => 'field'],
    'Long Description'          => ['target' => 'DETAIL_TEXT',   'type' => 'field'],
    'Short Description'         => ['target' => 'PREVIEW_TEXT',  'type' => 'field'],
    'Brand'                     => ['target' => 'BRAND',         'type' => 'prop'],
    'Net Weight (kg)'           => ['target' => 'WEIGHT',        'type' => 'prop'],
    'Color'                     => ['target' => 'COLOR',         'type' => 'prop'],
    'Country of Origin'         => ['target' => 'COUNTRY_ORIGIN','type' => 'prop'],
    'GTIN'                      => ['target' => 'CML2_BAR_CODE', 'type' => 'prop'],
    'Manufacturer SKU'          => ['target' => 'CML2_ARTICLE',  'type' => 'prop'],
];

Synchronization

function syncSalsifyAgent(): string
{
    $lastSync = \Bitrix\Main\Config\Option::get('salsify_sync', 'last_run', '');
    $client   = new SalsifyClient(SALSIFY_ORG_ID, SALSIFY_API_KEY);
    $mapping  = include '/local/config/salsify-mapping.php';

    $page    = 1;
    $newSync = date('c');

    do {
        $response = $client->getProducts($page, 100, $lastSync ?: null);
        $products = $response['products'] ?? [];

        foreach ($products as $product) {
            importSalsifyProduct($product, $mapping, $client);
        }

        $page++;
        $meta = $response['meta'] ?? [];
    } while (($meta['current_page'] ?? 1) < ($meta['total_pages'] ?? 1));

    \Bitrix\Main\Config\Option::set('salsify_sync', 'last_run', $newSync);
    return __FUNCTION__ . '();';
}

function importSalsifyProduct(array $product, array $mapping, SalsifyClient $client): void
{
    $attributes = $product['attributes'] ?? [];
    $sku        = $product['salsify:id'];

    // Get attribute values
    $getValue = static function (array $attrs, string $key): mixed {
        return $attrs[$key] ?? null;
    };

    $fields = ['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'ACTIVE' => 'Y'];
    $props  = [];

    foreach ($mapping as $salsifyKey => $config) {
        $value = $getValue($attributes, $salsifyKey);
        if ($value === null) continue;

        // Convert arrays to string via separator
        if (is_array($value)) {
            $value = implode(', ', $value);
        }

        if ($config['type'] === 'field') {
            $fields[$config['target']] = $value;
        } else {
            $props[$config['target']] = $value;
        }
    }

    $existing = CIBlockElement::GetList(
        [], ['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'PROPERTY_CML2_ARTICLE' => $sku]
    )->Fetch();

    $el = new CIBlockElement();

    if ($existing) {
        $productId = $existing['ID'];
        $el->Update($productId, $fields);
    } else {
        $productId = $el->Add($fields);
    }

    if ($productId) {
        CIBlockElement::SetPropertyValuesEx($productId, CATALOG_IBLOCK_ID, $props);

        // Media files
        $primaryImage = $product['salsify:primary_image'] ?? null;
        if ($primaryImage) {
            importSalsifyAsset($productId, $primaryImage, $client);
        }
    }
}

Salsify Specifics

Complex attribute values. Salsify returns attributes as an array where each element can be a scalar value, object, or list. Enum attributes return {"salsify:id": "...", "salsify:name": "Red"} — need to extract salsify:name.

Prices. Salsify is not a pricing system. Prices are typically not synchronized from Salsify — they come from 1C. Check with the client whether Salsify is a price source or only content.

Media files. Salsify stores media files with CDN links. You can avoid downloading files and store external URLs in the EXTERNAL_IMAGE_URL property, pulling them on the fly via <img src="...">. This saves server space for Bitrix.

Webhook. Salsify supports webhooks when products change. Configure in Salsify → Integrations → Webhooks. The handler URL receives a POST request with product_id, then requests current data via API.

Implementation Timeline

Scope Components Timeline
Up to 5,000 SKU, basic content Client + mapping + agent 1–2 weeks
10,000–100,000 SKU + media + webhooks + processing complex attributes + optimization 3–4 weeks
Multi-regional catalog (different prices, content by region) + Salsify channels logic + Bitrix multisite 5–7 weeks