ZR Express API for Node.js and PHP

Create, track, list and cancel your ZRExpress parcels with the dz-delivery SDK — plus PDF labels, bulk creation and webhook verification.

Install

TypeScript · npm
npm install @dz-delivery/core
PHP · Composer
composer require dz-delivery/sdk

Node.js ≥ 18 (zero dependencies) or PHP ≥ 8.2 (Guzzle 7). Every carrier implements the same interface, so the same code works with the others once the client is configured; some operations are not available on every carrier and throw NOT_SUPPORTED (see the capability matrix).

ZRExpress credentials

OptionHeader sentWhere to find it
apiKeyX-Api-KeyZRExpress portal (secret API key)
tenantX-TenantZRExpress portal (tenant ID)

Official documentation: docs.zrexpress.app.

ZRExpress with TypeScript / Node.js

TypeScript
import { createClient, isShippingError } from '@dz-delivery/core';

const zr = createClient('zrexpress', {
  apiKey: process.env.ZREXPRESS_API_KEY!,
  tenant: process.env.ZREXPRESS_TENANT!,
});

try {
  const wilayas = await zr.getWilayas();               // [{ id, name, code: '16' }]
  const communes = await zr.getCommunes(16);           // ZR id or official code 1–58
  const hubs = await zr.getStopDesks({ wilayaId: 16 }); // pickup points

  const quote = await zr.getFees({ toWilayaId: 16, isStopDesk: false });
  // { deliveryFee: 400, stopDeskFee: 250, currency: 'DZD' }

  const parcel = await zr.createParcel({
    recipientName: 'Mohamed Amine',
    phone: '0550 12 34 56',          // normalized to +213…
    address: 'Cité 221 lots, n°98',
    wilayaName: 'Alger',
    communeName: 'Bab Ezzouar',
    productName: 'T-shirt',
    price: 2500,                     // amount collected, fees included (≤ 150,000 DZD)
    orderId: 'ORDER-1001',
  });

  const { parcel: current, events } = await zr.track(parcel.trackingNumber);

  // Sync the account's parcels (pull)
  const page = await zr.listParcels({ updatedSince: '2026-10-01T00:00:00+00:00', allPages: true });

  await zr.cancelParcel(parcel.trackingNumber); // tracking number or UUID
} catch (err) {
  if (isShippingError(err)) console.error(err.code, err.message);
}

ZRExpress with PHP / Laravel

PHP
use DzDelivery\ClientFactory;
use DzDelivery\Errors\ShippingError;

$zr = ClientFactory::create('zrexpress', [
    'apiKey' => getenv('ZREXPRESS_API_KEY'),
    'tenant' => getenv('ZREXPRESS_TENANT'),
]);

try {
    $quote = $zr->getFees(['toWilayaId' => 16, 'isStopDesk' => false]);
    $hubs  = $zr->getStopDesks(['wilayaId' => 16]);

    $parcel = $zr->createParcel([
        'recipientName' => 'Mohamed Amine',
        'phone'         => '0550123456',
        'address'       => 'Cité 221 lots, n°98',
        'wilayaName'    => 'Alger',
        'communeName'   => 'Bab Ezzouar',
        'productName'   => 'T-shirt',
        'price'         => 2500,
        'orderId'       => 'ORDER-1001',
    ]);

    $result = $zr->track($parcel->trackingNumber);
    $page   = $zr->listParcels(['orderId' => 'ORDER-1001']);
} catch (ShippingError $e) {
    echo "{$e->code}: {$e->getMessage()}";
}

Installation, Laravel, errors and testing: see the full PHP guide.

Supported methods

MethodSupportNotes
createParcelyesThe SDK reads the new parcel back to return its tracking number.
trackyesUnified status + state history.
getFeesyesHome and pickup-point rates for the wilaya.
getWilayas / getCommunesyesZR territories; official code or name accepted.
getStopDesksyesZR hubs that act as pickup points.
listParcelsyesPaginated search, filters orderId, keyword, status, updatedSince.
cancelParcelyesTracking number or UUID.
healthCheckyesChecks the credentials (real API call).

ZRExpress labels, bulk shipping and webhooks

These ZRExpress-specific features are available through createZRExpress() (TS) or new ZRExpressProvider() (PHP).

TypeScript
import { createZRExpress, verifyZRExpressWebhook } from '@dz-delivery/core';

const zr = createZRExpress({ apiKey, tenant });

// PDF labels (max 250)
const { fileUrl } = await zr.generateLabels(['16-AAA111-ZR', '16-BBB222-ZR']);

// Bulk create (max 100, orderId required)
const { successes, failures } = await zr.createParcelsBulk([
  { ...parcelInput, orderId: 'ORD-1' },
  { ...parcelInput, orderId: 'ORD-2' },
]);

// Signed webhooks (raw body required)
app.post('/webhooks/zrexpress', express.raw({ type: '*/*' }), (req, res) => {
  const event = verifyZRExpressWebhook(req.body, req.headers, process.env.ZREXPRESS_WEBHOOK_SECRET!);
  // event.type · event.trackingNumber · event.status (unified)
  res.sendStatus(200);
});
PHP
use DzDelivery\Models\ClientOptions;
use DzDelivery\Providers\ZRExpressProvider;
use DzDelivery\Providers\ZRExpress\Webhook;

$zr = new ZRExpressProvider(ClientOptions::fromArray(['apiKey' => getenv('ZREXPRESS_API_KEY'), 'tenant' => getenv('ZREXPRESS_TENANT')]));
$zr->createParcelsBulk([...]);

$raw   = file_get_contents('php://input');
$event = Webhook::verify($raw, getallheaders(), getenv('ZREXPRESS_WEBHOOK_SECRET'));

Details: ZREXPRESS.md.

ZRExpress statuses → unified statuses

ZRExpress names its states either as slugs (commande_recue) or in PascalCase (OutForDelivery): the SDK accepts both forms, case-insensitively. This mapping is still a draft and will be completed from real API responses.

ZRExpress state (examples)Unified status
commande_recue, OrderReceivedpending
pret_a_expedier, ReadyToDispatchcreated
confirme_au_bureau, OutForDelivery, other hub / transfer statesin_transit
Delivered, livre, encaissedelivered
Returned, retour*returned
Cancelled, annule, supprimeecancelled
Any other stateunknown

What each unified status means: full list of unified statuses.

Good to know about the ZRExpress API

  • Amount ≤ 150,000 DZD, delivery fee included (price).
  • Phone numbers are sent in international format (+213…): pass them as you have them, the SDK normalizes them.
  • Unless its state is changed in the portal, the parcel stays « Commande reçue » (order received, pending) until it is marked ready to dispatch.
  • Stop desks = ZR hubs that act as pickup points; filtering by wilaya only gives the best results.
  • Products: stockType defaults to none (no catalog). local / warehouse require productId + sku.
  • In production, prefer webhooks to follow status changes; listParcels is the pull alternative.

Detailed native mapping (endpoints, fields, statuses): PROVIDERS.md.

ZRExpress FAQ

Where do I find the ZRExpress API key and tenant?

In the ZRExpress portal: the API key goes into apiKey (header X-Api-Key) and the tenant ID into tenant (header X-Tenant).

How do I print ZR Express labels?

generateLabels([...trackings]) returns the URL of a combined PDF, or one PDF per parcel with { mode: 'individual' }.

How do I receive ZRExpress status changes?

Set up a webhook in the portal, then verify the signature with verifyZRExpressWebhook (TS) or Webhook::verify (PHP) on the raw request body.

Can I cancel a parcel with its tracking number?

Yes: cancelParcel accepts the tracking number or the ZR UUID.

Other carriers

Same interface, same unified statuses (pending, in_transit, delivered…).