Nova Poshta – get package status using CRON

Nova Poshta

Nova Poshta – get package status using CRON


we want to track what is happening with packages that are sent using the NovaPoshta Postal service
create a CRON job that will use package IntDocNumber and ask NS API about current package status

Nova Poshta (Нова пошта) is a Ukrainian postal and courier company. It’s a popular service for delivering orders from e-shops or auction platforms (like Rozetka or prom). NS has over 6000 branches, postal-offices in the entire country. In Ukraine, it’s a convenient way for package delivery. You can open your package at the office desk, check the content, pay using cash and take the package.

Our WooCommerce shop checkout provides the ability for the user to pick one of the 6000 branches to deliver the package. After address delivery validation, the order is completed and the package is sent. In order postmeta, we’re saving ‘IntDocNumber’, which is the tracking number for the package.

Nova Poshta API

There is an API that can be used for package tracking. With the apiKey generated we can call the “getStatusDocuments” method using the model “TrackingDocument”. Having updated info about the package, we can inform our Shop Customer about “what is happening” with his order, or even provide the estimated time of delivery.

Cron Job

Our WordPress Cron Job will fetch all completed WooCommerce orders and ask the NovaPoshta API about the current “Document status”. It will be executed every hour. Our WP is using the Multisite environment, so we’re going to gather orders separately for each blog site. Internally, data will be fetched using WP REST API for WooCommerce. Originally, WordPress is running a separate CRON queue for every sub-site. Our function handles all sub-sites in one function, so we’re going to schedule it to run only for the main blog site.

 * // functions.php
 * add action for checking NovaPoshta API package status
add_action('ct_cron_ns_status_check1', 'ct_get_ns_packages_status');
// setup cron for NovaPoshta
add_action('init', 'schedule_cron1');
function schedule_cron1(){
    if ( ! is_main_site() ) {
    if( !wp_next_scheduled( 'ct_cron_ns_status_check1' ) ) {
        wp_schedule_event( time(), 'hourly', 'ct_cron_ns_status_check1' );

The main function will be triggering a “status check” for every sub-site and logging the total time of execution and statistics into the WP Data Logger custom table. This will help later in debugging or tracking API calls.

 * Get NovaPoshta packages status
const CT_SHOPS = array(
    array("name" => "UA", "blog_id" => 1),
    array("name" => "RU", "blog_id" => 2),
function ct_get_ns_packages_status(){
    $time_start = microtime(true);
    // UA first
    $blog_site_1[] = CT_SHOPS[0];
    $count1 = ct_handle_single_site($blog_site_1);
    // then RU
    $blog_site_2[] = CT_SHOPS[1];
    $count2 = ct_handle_single_site($blog_site_2);
    $time_end = microtime(true);
    $execution_time = ($time_end - $time_start)/60;
    // log response
    $tracking_data = array(
        'total_time' => $execution_time,
        'total_completed_orders' => $count1['total_completed_orders'] + $count2['total_completed_orders'],
        'total_records_updates' => $count1['total_records_updates'] + $count2['total_records_updates']
    do_action( 'ctlogger', $tracking_data, 'cronPackStatus', '' );

PHP helper function ct_handle_single_site() gathers all package numbers for completed WooCommerce orders, creating chunks of 50 items and calling NovaPoshta API. After a successful response, the woo order post meta is updated with the Status Code and Status values received from API.

function ct_handle_single_site($blog){
    $orders_data = ct_get_package_numbers($blog);
    $total_records_updates = 0;
     * split array (50 packages per API call)
    $chunk_size = 50;
    foreach (array_chunk($orders_data['packages_numbers'], $chunk_size, true) as $packages_chunk) :
        $tracking_data = ct_ns_get_package_info($packages_chunk);
        // log response
        $log_key = 'getStatusDocuments_'. $blog[0]['name'];
        do_action( 'ctlogger', $tracking_data, $log_key, '' );
        $data_for_sql_update = array();
             return false;
        // combine arrays : $orders_data['packages_numbers'] and $tracking_data
        foreach($tracking_data['data'] as $package){
            $temp1 = array();
                $temp1['Number'] = isset($package['Number']) ? $package['Number'] : '';
                $temp1['StatusCode'] = isset($package['StatusCode']) ? $package['StatusCode'] : '';
                $temp1['Status'] = isset($package['Status']) ? $package['Status'] : '';
                $order_number = $packages_chunk[$package['Number']]['order_number'];
                $data_for_sql_update[$order_number] = $temp1;
        // switch to subsite
        foreach($data_for_sql_update as  $item_order_number => $item){
            // UA/571/2021
            $order_number_array = ctParseOrderNumber($item_order_number);
            if(! empty($order_number_array)){
                $site_id = $order_number_array['site_id'];
                // additional caution
                if($site_id == $blog[0]['blog_id']){
                    $res1 = update_post_meta($order_number_array['order_id'], 'ctIntDocNumber', $item['Number']);
                    $res2 = update_post_meta($order_number_array['order_id'], 'ctIntDocStatusCode', $item['StatusCode']);
                    $res3 = update_post_meta($order_number_array['order_id'], 'ctIntDocStatus', $item['Status']);
                    $res4 = update_post_meta($order_number_array['order_id'], 'ctIntDocRecentUpdate', time());
    $res = array(
        'total_records_updates' => $total_records_updates,
        'total_completed_orders' => $orders_data['total_completed_orders'],
        'packages_numbers' => $orders_data['packages_numbers']
    return $res;

Connecting to NovaPoshta API

This function is probably the most interesting one. It creates an API request to . The method “getStatusDocuments” can handle up to 100 items in one request. Make sure to generate your own API Key and update the apiKey value in the code.

 * @return mixed
 * "modelName": "TrackingDocument",
 * "calledMethod": "getStatusDocuments",
 * (up to 100 items in 1 request)
function ct_ns_get_package_info($params){
    // convert to api format
    $params = array_values($params);
    try {
        $data['modelName'] = 'TrackingDocument';
        $data['calledMethod'] = 'getStatusDocuments';
        $data['apiKey'] = 'abc123';
        $data['methodProperties'] = [
            'Documents' => $params
        $apiUrl = '';
        $body = wp_json_encode( $data);
        $options = [
            'body'        => $body,
            'headers'     => [
                'Content-Type' => 'application/json',
            'timeout'     => 60,
            'redirection' => 5,
            'blocking'    => true,
            'httpversion' => '1.0',
            'sslverify'   => false,
            'data_format' => 'body',
        $response = wp_remote_post( $apiUrl, $options );
       if ( is_wp_error( $response ) ) {
           $error_message = $response->get_error_message();
           echo "Something went wrong: $error_message";
        $api_response = json_decode( wp_remote_retrieve_body( $response ), true );
        if (isset($api_response['success']) && true === $api_response['success']) {
            return $api_response;
       return false;
    } catch (ApiServiceException $e) {
        return $this->jsonResponse([
            'success' => false,
            'exception' => $e->getMessage()

WooCommerce API – get all orders

To gather all package numbers that are added in post meta orders, we will use the WP_REST_Request() internal |WooCommerce API call. As parameter – status = completed is defined, which will query only orders that are already sent via the NovaPoshta courier.

 * Woocommerce - get all completed orders for single sub-site
 * Woocommerce API (internal call)
function ct_get_package_numbers($blog){
    $blog = is_array($blog[0]) ? $blog[0] : $blog;
    $request = new WP_REST_Request( 'GET', '/wc/v3/orders' );
    $request->set_query_params( [ 'status' => 'completed' ] );
    $response = rest_do_request( $request );
    $server = rest_get_server();
    $completed_orders = $server->response_to_data( $response, false );
        return new WP_REST_Response( $completed_orders, 401 );
    $packages_numbers = array();
    foreach ($completed_orders as $order) {
        // package number is stored in WooCommerce order meta
        $ttn_number = '';
        foreach($order['meta_data'] as $metadata){
            if($metadata->key == 'ctIntDocNumber') {
                $ttn_number = isset($metadata->value) ? $metadata->value : '';
        if($ttn_number != ''){
            $packages_numbers[$ttn_number] = [
                'DocumentNumber' => $ttn_number,
                'Phone' => '',
                'order_number' => $order['number']
    $response = array(
        'total_completed_orders' => count($completed_orders),
        'packages_numbers' => $packages_numbers
    return $response;

It’s important to mention that WordPress Cron operates outside the wp_user scope. For a WP Rest API proper response, we need to authenticate as a user with administrator permissions (that can handle the multisite network area).

function ct_cron_authenticate_user(){
    if ( defined( 'DOING_CRON' ) ){
        // Notice - CRON job is running out of user context
        // To call internal API endpoint we will - hardcode user_id and authenticate the user
        // user_id = 1 = superadmin (with network privileges)
        wp_set_current_user ( 1 );

Testing NovaPoshta CRON

The WP Cli command line is a useful tool that can be used for testing CRON jobs. By executing this single command, our function will be executed in the CRON context.

// test single cron job event
wp cron event run ct_cron_ns_status_check1

Now, we can check WooCommerce order meta data. The Status and Status Code should be properly applied from the NovaPoshta API response. Data is stored in wp_postmeta and also available through the default WooCommerce API endpoint /wp-json/wc/v3/orders?status=completed

"meta_data": [
        "id": 3409,
        "key": "ctIntDocNumber",
        "value": "59000218530814"
        "id": 3410,
        "key": "ctEstimatedDeliveryDate",
        "value": "03.03.2015"
        "id": 3413,
        "key": "ctIntDocStatusCode",
        "value": "3"
        "id": 3414,
        "key": "ctIntDocStatus",
        "value": "Номер не найден"
        "id": 3417,
        "key": "ctIntDocRecentUpdate",
        "value": "1631883109"

NovaPoshta tracking statuses

The status of shipment returned by the NS API is represented by a Status Code number and description Status. The list of all statuses can be found in the official API documentation:

Here is a list of English translations of shipment statuses. Keep in mind that the list below may not be complete/can be outdated. My recommendation will be to refer to the original Ukrainian docs first.

List of possible code and status values (ENG)

method “getStatusDocuments”

model “TrackingDocument”

1Nova Poshta is waiting for receipt from a sender
3Number not found
4Shipment in the Sender’s city
41Shipment in the Sender’s city (the status for local standard and local express services – delivery is within the city)
5Shipment goes to the Recipient’s city
6Shipment is in the Recipient’s city, an indicative delivery to the warehouse – XXX dd-mm. Expect an additional message about arrival.
7, 8Arrived at the warehouse.
9Shipment is received
10Shipment is received %DateReceived%. Within 24 hours, you will receive an SMS message about money transfer and will be able to receive it at the cash desk of the New Poshta warehouse.
11Shipment is received %DateReceived%. The money transfer is given to the Recipient.
14Shipment is transmitted to Recipient for checkup
101On the way to the Recipient
102, 103, 108Recipient’s refusal
104Address changed
105Storage is stopped
106Express invoice of return delivery is received and created

That’s it for today’s tutorial. Make sure to follow us for other useful tips and guidelines.

Need help?

  • Looking for support from experienced programmers?

  • Need to fix a bug in the code?

  • Want to customize your webste/application?


Your email address will not be published.

createIT Contact