Business APIv1
Open web app ↗

Courier Express API Documentation

Create pickup & delivery orders and track them from your own system.

Get your API keys

You need three values before calling the API.

Base URLhttps://api.courierexpressmalta.com
Client ID & SecretWeb app → Settings → API KeysOpen ↗
Service IDSent to you by Courier Express
Keep your Client Secret and tokens on your server. Never put them in a website or app.

Get an access token

POST

Send your Client ID and Secret. Use the returned token on every other request.

POST/api/v1/customer/auth/access-token
Request
JSON body
{
  "clientId": "YOUR_CLIENT_ID",
  "clientSecret": "YOUR_CLIENT_SECRET"
}
Response · 200 OK
JSON
{
  "code": 0,
  "message": "",
  "data": {
    "customerId": "YOUR_CUSTOMER_ID",
    "accessToken": "ACCESS_TOKEN",
    "expiresIn": 3600
  },
  "metadata": {}
}
Send it as Authorization: Bearer ACCESS_TOKEN. It expires after expiresIn seconds.

Create an order

POST

Create a pickup & delivery order, with optional cash on delivery.

POST/api/v1/customer/order/pickup-delivery
Request
JSON body · example with COD
{
  "customerId": "YOUR_CUSTOMER_ID",
  "pickup": {
    "address": "Valletta, Malta",
    "addressDetail": "",
    "completeAfter": 0,
    "completeBefore": 0,
    "coordinates": [14.5146, 35.8992],
    "fullName": "Sender Name",
    "phone": "+35620000000",
    "email": "sender@example.com",
    "placeId": ""
  },
  "delivery": {
    "address": "Sliema, Malta",
    "addressDetail": "Apartment 2",
    "completeAfter": 0,
    "completeBefore": 0,
    "coordinates": [14.5010, 35.9122],
    "fullName": "Recipient Name",
    "phone": "+35620000001",
    "email": "recipient@example.com",
    "placeId": ""
  },
  "service": {
    "id": "YOUR_SERVICE_ID",
    "options": []
  },
  "paymentMethod": "Cash",
  "paymentSide": "Sender",
  "draft": false,
  "codAmount": 25,
  "uid": "UNIQUE_ORDER_001",
  "note": "Handle with care",
  "referenceId": "ORDER-001"
}
After creating the order, print its label: get shipmentLabelUrl from step 4.
Coordinates are [longitude, latitude]. Times are Unix milliseconds; use 0 for no schedule.
Fields
FieldDescription
customerIdFrom the access-token response
service.idYour Service ID from Courier Express
service.optionsOptional; use [] if none
pickup / deliveryContact, address, coordinates and time window
paymentMethodCash or Wallet
paymentSideSender or Receiver
draftfalse = confirmed order, true = draft
codAmountCash to collect on delivery; 0 for none
uidOptional unique ID to prevent duplicates
referenceIdOptional reference from your system
Who pays the delivery fee
OptionpaymentMethodpaymentSideFee paid by
WalletWalletSenderYour account wallet
Cash · receiverCashReceiverRecipient, at delivery
Cash · senderCashSenderSender, at pickup

Available options depend on your service. codAmount is separate from the delivery fee.

Check an order

GET

Get the latest details and status using the orderId from step 3.

GET/api/v1/customer/order/{orderId}?customerId={customerId}
Response
200 OK · selected fields
{
  "code": 0,
  "message": "",
  "data": {
    "id": "YOUR_ORDER_ID",
    "status": "Confirmed",
    "type": "PickupDelivery",
    "code": "4737926",
    "price": 10,
    "paymentMethod": "Wallet",
    "paymentSide": "Sender",
    "codAmount": 0,
    "trackOrder": "https://example.com/track/YOUR_ORDER",
    "shipmentLabelUrl": "https://example.com/shipment-label/YOUR_ORDER",
    "referenceId": "ORDER-001"
  }
}
Every package must have a label. Open shipmentLabelUrl, print it and stick it on the package before pickup.

The full response has more order, pickup, delivery and tracking fields.

Order statuses, in order
DraftConfirmedPickupRoutedReadyForPickupPickedUpAtWarehouseDeliveryRoutedReadyForDeliveryOutForDeliveryDelivered
Exceptions
NotDeliveredForReturnReturnedPickupFailedCustomerCanceledSupportCanceledLost

Receive webhooks

Incoming

Get status updates pushed to your server instead of polling.

  1. Create an HTTPS endpoint on your serverYou
  2. Send the endpoint URL to our support teamYou
  3. Support adds it to your accountCourier Express
  4. Support shares your webhook secretCourier Express
  5. Verify each request's signature, then process itYou
Example payload
POST to your endpoint · selected fields
{
  "timestamp": 1758028850388,
  "data": {
    "trigger": "Updated",
    "id": "YOUR_ORDER_ID",
    "status": "NotDelivered",
    "type": "PickupDelivery",
    "customerId": "YOUR_CUSTOMER_ID",
    "code": "5184194",
    "paymentMethod": "Cash",
    "paymentSide": "Receiver",
    "codAmount": 32500,
    "shipmentLabelUrl": "https://example.com/shipment-label/12345",
    "referenceId": "REF_0002/12345"
  }
}

The real payload also includes pickup, delivery, driver, service and proof details.

Verify the signature · Node.js
HMAC-SHA256 · header x-webhook-signature
const crypto = require('crypto');

// Use the exact payload serialization expected by the sender.
const body = JSON.stringify(req.body);
const expected = crypto
  .createHmac('sha256', process.env.WEBHOOK_SECRET)
  .update(body)
  .digest('hex');

const received = req.get('x-webhook-signature') || '';
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(received, 'hex');

if (a.length !== b.length ||
    !crypto.timingSafeEqual(a, b)) {
  return res.sendStatus(401);
}
// Signature is valid — handle the update.
Store the secret from support as WEBHOOK_SECRET on your server. Reject requests whose signature doesn't match.

Print shipment labels

POST

Every package must carry its label. There are two ways to get it.

One orderUse shipmentLabelUrl from the order detailsStep 4 →
Many ordersDownload one PDF with this endpoint
POST/api/v1/customer/order/shipment-label/export-pdf
Request
JSON body
{
  "customerId": "YOUR_CUSTOMER_ID",
  "ids": [
    "ORDER_ID_1",
    "ORDER_ID_2"
  ]
}
FieldDescription
customerIdFrom the access-token response
idsList of orderId values to print
The response is a PDF file. Save it or send it straight to your printer.

shipmentLabelUrl is also included in every webhook payload.

Cancel an order

POST

Cancel an order before it is picked up. Optionally give a reason.

1 · Get the list of reasons
GET/api/v1/customer/order/pickup-delivery/cancellation-reason
Response · 200 OK
{
  "code": 0,
  "message": "",
  "data": [
    {
      "id": "REASON_ID",
      "text": "I want to change my order details",
      "type": "Cancellation"
    }
  ],
  "metadata": {}
}
2 · Cancel the order
POST/api/v1/customer/order/pickup-delivery/cancel
JSON body
{
  "orderId": "YOUR_ORDER_ID",
  "customerId": "YOUR_CUSTOMER_ID",
  "failureReasonId": "REASON_ID",
  "failureReasonText": "I want to change my order details"
}
FieldRequiredDescription
orderIdYesThe order to cancel
customerIdYesFrom the access-token response
failureReasonIdNoAn id from the reasons list
failureReasonTextNoThe matching reason text
Only orders that haven't been picked up yet can be cancelled. The status becomes CustomerCanceled.