Integrating 1C-Bitrix with the DPD delivery service

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

1C-Bitrix Integration with DPD Delivery Service

DPD is an international courier service with strong coverage in Russia and Europe. It differs from most Russian carriers by using a SOAP interface instead of REST. This is not a problem, but requires a different approach: working with WSDL, SoapClient, and a specific data format. Integration with 1C-Bitrix is implemented through the standard custom delivery service mechanism.

DPD API: SOAP Interface

DPD provides several WSDL services:

  • https://ws.dpd.ru/services/calculator2?wsdl — cost calculation
  • https://ws.dpd.ru/services/geography2?wsdl — cities and pickup points
  • https://ws.dpd.ru/services/order2?wsdl — order creation
  • https://ws.dpd.ru/services/tracking2?wsdl — tracking

Authorization is identical for all services: the client number (clientNumber) and key (clientKey) are passed in every request as part of the auth structure.

private function createSoapClient(string $wsdl): \SoapClient
{
    return new \SoapClient($wsdl, [
        'soap_version' => SOAP_1_1,
        'trace'        => true,
        'exceptions'   => true,
        'cache_wsdl'   => WSDL_CACHE_DISK,
        'encoding'     => 'utf-8',
    ]);
}

private function getAuth(): array
{
    return [
        'clientNumber' => $this->getOption('CLIENT_NUMBER'),
        'clientKey'    => $this->getOption('CLIENT_KEY'),
    ];
}

WSDL caching (WSDL_CACHE_DISK) is mandatory — without it, every request downloads the schema, which is critical for cost calculation on the checkout page.

Delivery Cost Calculation

private function calcCost(
    string $fromCityCode,
    string $toCityCode,
    int $weightGram
): float {
    $client = $this->createSoapClient(
        'https://ws.dpd.ru/services/calculator2?wsdl'
    );

    $request = [
        'auth'         => $this->getAuth(),
        'pickup'       => ['cityId' => (int)$fromCityCode],
        'delivery'     => ['cityId' => (int)$toCityCode],
        'selfPickup'   => false,
        'selfDelivery' => false,
        'weight'       => $weightGram / 1000, // grams → kg (float)
        'volume'       => 0.001, // minimum volume
    ];

    $response = $client->getServiceCost2($request);
    $services = $response->return ?? [];

    // Find the "DPD Classic" tariff (serviceCode = 'CUR')
    foreach ((array)$services as $service) {
        if ($service->serviceCode === 'CUR') {
            return $service->cost;
        }
    }

    return 0;
}

DPD returns the cost for all available tariffs. Main ones: CUR (DPD Classic — door-to-door delivery), PCL (DPD Online Express), ECO (DPD Economy). The tariff code is selected in the module settings.

DPD City Mapping

DPD uses its own city identifiers (cityId). Conversion from city name or CLADR code:

public function findCity(string $cityName): ?int
{
    $client = $this->createSoapClient(
        'https://ws.dpd.ru/services/geography2?wsdl'
    );

    // Cache the result for 24 hours
    $cacheKey = 'dpd_city_' . md5($cityName);
    $cached = \Bitrix\Main\Data\Cache::createInstance();

    if ($cached->startDataCache(86400, $cacheKey, '/dpd')) {
        $result = $client->getCitiesCashPay([
            'auth'     => $this->getAuth(),
            'cityName' => $cityName,
        ]);
        $cities = (array)($result->return ?? []);
        $cityId = !empty($cities) ? $cities[0]->cityId : null;
        $cached->endDataCache(['cityId' => $cityId]);
    } else {
        $cityId = $cached->getVars()['cityId'];
    }

    return $cityId;
}

The 24-hour cache is critical: the DPD city list does not change hourly, and without caching every cost calculation makes two SOAP requests.

Creating a DPD Order

public function createOrder(\Bitrix\Sale\Shipment $shipment): string
{
    $client = $this->createSoapClient(
        'https://ws.dpd.ru/services/order2?wsdl'
    );

    $order = $shipment->getOrder();
    $props = $order->getPropertyCollection();

    $request = [
        'auth' => $this->getAuth(),
        'header' => [
            'datePickup'   => date('Y-m-d', strtotime('+1 day')),
            'senderAddress' => $this->getSenderAddress(),
            'pickupTimePeriod' => '9:00-18:00',
        ],
        'order' => [[
            'orderNumberInternal' => (string)$order->getId(),
            'serviceCode'         => 'CUR',
            'serviceVariant'      => 'ДД', // door-to-door
            'weight'              => $this->getWeight($shipment) / 1000,
            'declaredValue'       => $order->getPrice(),
            'cargoRegistered'     => false,
            'cargoCategory'       => 'Товар',
            'receiverName'        => $props->getItemByOrderPropertyCode('FIO')?->getValue(),
            'receiverPhone'       => $props->getItemByOrderPropertyCode('PHONE')?->getValue(),
            'receiverAddress'     => $this->buildReceiverAddress($props),
        ]],
    ];

    $response = $client->createOrder($request);
    $result = $response->return->order[0] ?? null;

    return $result->orderNum ?? '';
}

Tracking

public function trackOrder(string $dpdOrderNum): array
{
    $client = $this->createSoapClient(
        'https://ws.dpd.ru/services/tracking2?wsdl'
    );

    $response = $client->getStatesByOrderNum([
        'auth'     => $this->getAuth(),
        'orderNum' => $dpdOrderNum,
    ]);

    $states = (array)($response->return->states ?? []);
    $last = end($states);

    return [
        'status'    => $last->newState ?? '',
        'city'      => $last->city ?? '',
        'timestamp' => $last->transitionTime ?? '',
    ];
}

DPD does not provide push webhooks. Only polling — a 1C-Bitrix agent runs every 2–3 hours for active orders.

Timelines

Scope Timeline
SOAP client + calculation + city mapping 3–4 days
+ Order creation + tracking polling +2 days
+ DPD pickup points on map +2 days