Integration of 1C-Bitrix with the UkrPoshta delivery service (Ukraine)

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 Ukrposhta Delivery Service (Ukraine)

Ukrposhta is the state postal operator of Ukraine. Like Russian Post, it is indispensable for delivery to small towns and rural areas. The API is modern (REST, OAuth 2.0), but has a number of specific requirements: mandatory address normalization, strict data formats. For stores covering all of Ukraine, integration with Ukrposhta often runs in parallel with Nova Poshta.

Ukrposhta API: OAuth 2.0

Base URL: https://www.ukrposhta.ua/ecom/0.0.1/. Authorization: Bearer token. The token is obtained via OAuth 2.0 with the client_credentials grant type.

private function getToken(): string
{
    $cacheKey = 'ukrposhta_token';
    $cached = \Bitrix\Main\Data\Cache::createInstance();

    if ($cached->startDataCache(3000, $cacheKey, '/ukrposhta')) {
        $ch = curl_init('https://www.ukrposhta.ua/oauth/token');
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => http_build_query([
                'grant_type'    => 'client_credentials',
                'client_id'     => $this->clientId,
                'client_secret' => $this->clientSecret,
            ]),
            CURLOPT_RETURNTRANSFER => true,
        ]);
        $response = json_decode(curl_exec($ch), true);
        $token = $response['access_token'] ?? '';
        $cached->endDataCache(['token' => $token]);
    }

    return $cached->getVars()['token'];
}

The token is cached for 3000 seconds (token TTL is 3600 seconds, leaving a buffer).

Cost Calculation

public function calcPostcardPrice(
    string $fromPostcode,
    string $toPostcode,
    int $weightGram,
    string $type = 'PARCEL'
): float {
    $response = $this->apiGet('/delivery360/calculate', [
        'from_postcode' => $fromPostcode,
        'to_postcode'   => $toPostcode,
        'weight'        => $weightGram,
        'length'        => 300,
        'width'         => 200,
        'height'        => 100,
        'type'          => $type, // PARCEL, DOCUMENT, LETTER
    ]);

    return (float)($response['deliveryPrice'] ?? 0);
}

Calculation requires dispatch and delivery postal codes. To obtain a postal code from an address, use the address search method: GET /addresses/search?street=...&city=....

Address Normalization and Search

public function searchAddress(string $city, string $street, string $house = ''): array
{
    $response = $this->apiGet('/addresses/search', [
        'street'       => $street,
        'city'         => $city,
        'houseNumber'  => $house,
        'language'     => 'UA',
    ]);

    return $response['addresses'] ?? [];
}

The response contains an array of addresses with postcode — the postal code needed for calculation and shipment creation. If the address is not found or is ambiguous — show the customer a list of options for clarification.

Shipment Creation

public function createShipment(\Bitrix\Sale\Shipment $shipment): string
{
    $order = $shipment->getOrder();
    $props = $order->getPropertyCollection();

    $addressRef = $props->getItemByOrderPropertyCode('UKRPOSHTA_ADDRESS_ID')?->getValue();

    $payload = [
        'type'           => 'PARCEL',
        'sender'         => [
            'name'    => $this->getOption('SENDER_NAME'),
            'phone'   => $this->getOption('SENDER_PHONE'),
            'postcode' => $this->getOption('SENDER_POSTCODE'),
        ],
        'recipient'      => [
            'name'        => $props->getItemByOrderPropertyCode('FIO')?->getValue(),
            'phone'       => $props->getItemByOrderPropertyCode('PHONE')?->getValue(),
            'postcode'    => $props->getItemByOrderPropertyCode('UKRPOSHTA_POSTCODE')?->getValue(),
            'addressId'   => $addressRef,
        ],
        'weight'         => $this->getWeight($shipment),
        'length'         => 300,
        'width'          => 200,
        'height'         => 100,
        'declaredPrice'  => $order->getPrice(),
        'postPay'        => 0, // cash on delivery (0 = no COD)
    ];

    $response = $this->apiPost('/shipments', $payload);
    $barcode = $response['barcode'] ?? '';

    $props->getItemByOrderPropertyCode('UKRPOSHTA_BARCODE')?->setValue($barcode);
    $order->save();

    return $barcode;
}

Tracking

public function trackShipment(string $barcode): array
{
    $response = $this->apiGet('/statuses/shipments', [
        'barcode' => $barcode,
    ]);

    $event = end($response['eventHistory'] ?? []);
    return [
        'status'    => $event['eventName'] ?? '',
        'date'      => $event['date'] ?? '',
        'postcode'  => $event['postcode'] ?? '',
    ];
}

No push notifications. Polling via agent every 4–6 hours (Ukrposhta delivery times are 2–7 days, frequent polling is excessive).

Cash on Delivery

Ukrposhta supports cash on delivery (postPay). The amount is specified in hryvnias. Upon delivery, the customer pays the amount, Ukrposhta retains a commission and transfers the remainder to the store's account details from the contract.

Timelines

Scope Timeline
OAuth + cost calculation 2–3 days
+ Shipment creation + tracking +2 days
+ Address/postcode search at checkout +1 day