Skip to content

Order tracking ​

Page AI Agent → Skills → Order tracking in the dashboard (app.thehumind.com).

The support agent can answer "where is my order?" on its own. The visitor gives an order number and the email used at purchase, the agent looks the order up, and the chat shows a card with the fulfillment status, the carrier tracking link, the delivery timeline and the items. The agent then summarizes it in the conversation.

Nothing to write on your side if you are on Shopify. Everyone else (PrestaShop, WooCommerce, Magento, a custom OMS or ERP) exposes one HTTPS endpoint that Humind calls at lookup time. This page documents that endpoint.

Which integration applies ​

Your stackWhat to do
Shopify (Humind app installed)Nothing. Orders are looked up live in your store through the Admin API. The Skills page shows "Connected via Shopify".
Any other platformExpose the endpoint described below and register it under AI Agent → Skills → Order tracking → Custom API.

A few rules:

  • The tool switches itself on as soon as an integration is detected. You can turn it off from the Skills page, like any other skill.
  • If a custom API is enabled and a Shopify store is connected, the custom API wins.
  • Humind does not sync your order database. Every lookup is a live call to your endpoint, and only the fields listed in the contract reach the conversation. The order shown to the visitor is kept in the conversation history, like the rest of the thread, and is visible in your inbox.

How a lookup works ​

  1. The visitor asks about an order. If the order number or the email is missing, the chat shows a small form asking for both.
  2. Humind sends POST to your endpoint with the pair { orderNumber, email }.
  3. Your endpoint answers { "found": false } or { "found": true, "order": { ... } }.
  4. Humind validates the response, drops unknown fields and renders the card.

Guardrails on the Humind side, so you know what to expect:

RuleValue
Timeout8 seconds. Answer fast, from your database, not from a carrier API called synchronously.
RedirectsRefused. A redirect would leak your secret to another host. Answer on the URL you registered.
Failed lookups5 per conversation, then the agent stops trying. A found: false counts as a failure. A technical error (non-200, timeout, invalid response) does not count, and your endpoint may be called up to 3 times in total per attempt (2 retries), so keep the endpoint idempotent and apply your own rate limit if you need one.
HTTPSMandatory. HTTP URLs are rejected when you save the configuration.

Configure it in the dashboard ​

Open AI Agent → Skills, click Order tracking, then fill in the Custom API section:

  • Endpoint URL (HTTPS): the URL Humind will call.
  • Authentication: None, Bearer token (sent as Authorization: Bearer <secret>) or API key (header) (sent in the header whose name you choose, X-Api-Key by default).
  • Secret: written once, never displayed again. Leave it empty on later saves to keep the current one.
  • Use this API for order tracking: the switch that activates the integration.

Click Save, then use the Test section. It appears once a configuration is saved and always runs against the saved configuration, even when the switch is off. Enter a real order number and its purchase email and click Run test: it performs one real lookup and shows either the order found, "no order matches" (your endpoint answered correctly with found: false), or the error message.

Endpoint contract ​

Request ​

http
POST https://www.example.com/humind/track-order
Content-Type: application/json
Authorization: Bearer <secret>

{ "orderNumber": "1042", "email": "jane@example.com" }

orderNumber is what the visitor typed, trimmed but not otherwise normalized, 1 to 64 characters. It may or may not carry a # prefix and may be in any case: normalize it on your side (strip #, uppercase, trim) before searching. email arrives trimmed and lowercased; compare it case-insensitively on your side anyway.

Response when the order is not found, or the email does not match ​

json
{ "found": false }

Always answer 200 with found: false in that case. Do not answer 404 or 403: any non-200 status is treated as a technical failure and the visitor is told the lookup is unavailable, which is a worse experience than "no order matches these details".

Response when the order is found ​

json
{
  "found": true,
  "order": {
    "orderNumber": "1042",
    "createdAt": "2026-06-01T10:00:00Z",
    "fulfillmentStatus": "fulfilled",
    "financialStatus": "paid",
    "shipmentStatus": "out_for_delivery",
    "estimatedDeliveryAt": "2026-06-04T00:00:00Z",
    "statusPageUrl": "https://www.example.com/account/orders/1042",
    "deliveryMethod": "shipping",
    "trackingInfo": [
      { "company": "Colissimo", "number": "6A12345678901", "url": "https://www.laposte.fr/outils/suivre-vos-envois?code=6A12345678901" }
    ],
    "destination": { "address1": "12 rue de la Paix", "zip": "69001", "city": "Lyon", "country": "France" },
    "lineItems": [
      { "title": "Huile CBD 10%", "quantity": 1, "imageUrl": "https://www.example.com/img/huile-10.jpg" }
    ],
    "events": [
      { "status": "order_placed", "happenedAt": "2026-06-01T10:00:00Z" },
      { "status": "fulfilled", "happenedAt": "2026-06-02T08:00:00Z" },
      { "status": "in_transit", "happenedAt": "2026-06-03T06:30:00Z", "message": "Departed facility", "location": "Lyon, France" }
    ]
  }
}

Only orderNumber, createdAt and fulfillmentStatus are required. A minimal endpoint that returns just those three already gives the visitor a useful answer.

Fields ​

FieldRequiredTypeNotes
orderNumberyesstring, 1 to 64The number as the customer knows it.
createdAtyesISO 8601 dateOrder date.
fulfillmentStatusyesstring, up to 64Translated in the visitor's language when it is a known key: unfulfilled, partially_fulfilled, fulfilled, delivered, ready_for_pickup, picked_up, voided. Any other value is shown humanized in the language you wrote it (shipped shows as "Shipped" to a French visitor), so prefer the known keys.
financialStatusnostring, up to 64Translated keys: pending, authorized, paid, partially_paid, refunded, partially_refunded, voided. Any other value is shown humanized.
shipmentStatusnostring, up to 64Finer shipment status when you have it. When present it becomes the main badge of the card. Translated keys: in_transit, out_for_delivery, delivered, ready_for_pickup, picked_up. Any other value is shown humanized.
estimatedDeliveryAtnoISO 8601 dateDelivery estimate, shown on the card.
statusPageUrlnoURL, up to 2048A page on your site where the customer can see the full order. Must not require credentials the visitor does not have.
deliveryMethodnoshipping, pickup, localDefaults to shipping. pickup switches the card to its click-and-collect layout and shows pickupLocation instead of destination.
pickupLocationno{ name, address }The store or relay point, for pickup orders. Your address, not the customer's. name up to 160, address up to 256.
trackingInfonoarray, up to 20One entry per parcel: company (carrier name), number, url (the carrier tracking page). All strings up to 128 characters, url up to 2048.
destinationno{ address1, zip, city, country }Where the parcel is going. Street and zip are allowed because the visitor proved ownership of the order with the email. Never include the recipient's name or phone number. address1 up to 160, zip up to 32, city and country up to 128.
lineItemsnoarray, up to 50title (required, up to 256), quantity (integer from 1 to 10000, default 1), imageUrl (optional, up to 2048).
eventsnoarray, up to 20, chronologicalThe shipment journey. Each step has status (required, up to 64), happenedAt (required, ISO 8601), and optional message (up to 256) and location (up to 160). When absent, Humind adds a single order_placed step dated createdAt. The timeline is displayed once it has at least two steps.

events[].status is a lowercase key. Known keys are translated in the visitor's language: order_placed, confirmed, label_purchased, label_printed, fulfilled, ready_for_pickup, carrier_picked_up, picked_up, in_transit, out_for_delivery, attempted_delivery, delayed, failure, delivered. Any other key is shown humanized (left_warehouse becomes "Left warehouse").

Unknown fields are ignored. A field longer than its limit, an array longer than its maximum, or a null in an optional field makes the whole response invalid: the visitor sees "lookup unavailable" and the test panel shows "response does not match the expected contract" without further detail. Omit an optional field you do not have rather than sending null, and truncate on your side rather than sending everything you have.

Security checklist ​

  • Match both identifiers. Return the order only when the order number and the email belong to the same order. This is your half of the verification: Humind cannot check it for you. Anyone who knows an order number but not the email must get found: false.
  • Protect the endpoint with a bearer token or an API key header and reject requests without it. The secret is stored encrypted at Humind and only sent to the exact URL you registered.
  • No personal data beyond the contract. No recipient name, no phone number, no payment details. Street, zip and city are enough to reassure a customer about the destination.
  • Rate limit the endpoint if you like. Humind stops after 5 found: false answers per conversation, and each lookup is triggered by a visitor who typed both identifiers, but technical errors are retried and not capped.

Example: PrestaShop ​

On PrestaShop the customer knows the order reference (XKBKNABJK), so search by reference, not by id_order. A minimal module front controller looks like this. It is a starting point to adapt, not a drop-in module.

php
<?php
// modules/humindtracking/controllers/front/track.php
class HumindTrackingTrackModuleFrontController extends ModuleFrontController
{
    public function postProcess()
    {
        header('Content-Type: application/json');

        // Configured as "API key (header)" in the Humind dashboard, header name X-Api-Key.
        // Some Apache setups strip the Authorization header, so an API key header is the safer choice on PrestaShop.
        $secret = (string) Configuration::get('HUMIND_TRACKING_SECRET');
        $given = (string) ($_SERVER['HTTP_X_API_KEY'] ?? '');
        if ($secret === '' || !hash_equals($secret, $given)) {
            http_response_code(401);
            exit(json_encode(['error' => 'unauthorized']));
        }

        $input = json_decode((string) file_get_contents('php://input'), true) ?: [];
        $reference = strtoupper(trim(ltrim((string) ($input['orderNumber'] ?? ''), '#')));
        $email = strtolower(trim((string) ($input['email'] ?? '')));
        if ($reference === '' || $email === '') {
            exit(json_encode(['found' => false]));
        }

        // An order split into several shipments shares one reference: this example keeps the first one only.
        $orders = Order::getByReference($reference);
        $order = $orders->count() ? $orders->getFirst() : null;
        if (!$order) {
            exit(json_encode(['found' => false]));
        }
        $customer = new Customer((int) $order->id_customer);
        if (strtolower((string) $customer->email) !== $email) {
            exit(json_encode(['found' => false])); // same answer as "not found": never reveal that the reference exists
        }

        $carrier = new Carrier((int) $order->id_carrier);
        // The tracking number lives on order_carrier (Order::$shipping_number was removed in PrestaShop 8).
        $orderCarrier = new OrderCarrier((int) $order->getIdOrderCarrier());
        $trackingNumber = (string) $orderCarrier->tracking_number;

        $payload = [
            'orderNumber' => (string) $order->reference,
            'createdAt' => date('c', strtotime($order->date_add)),
            // Map your states to the keys the chat translates; $state->name would be shown untranslated to every visitor.
            'fulfillmentStatus' => $order->hasBeenDelivered() ? 'delivered' : ($order->hasBeenShipped() ? 'fulfilled' : 'unfulfilled'),
            'financialStatus' => $order->hasBeenPaid() ? 'paid' : 'pending',
        ];

        // Only set optional keys when you have a value: a JSON null fails validation on the Humind side.
        if ($order->hasBeenDelivered()) {
            $payload['shipmentStatus'] = 'delivered';
        } elseif ($order->hasBeenShipped()) {
            $payload['shipmentStatus'] = 'in_transit';
        }
        if ($trackingNumber !== '') {
            $payload['trackingInfo'] = [[
                'company' => (string) $carrier->name,
                'number' => $trackingNumber,
                'url' => $carrier->url ? str_replace('@', $trackingNumber, (string) $carrier->url) : '',
            ]];
        }

        $items = [];
        foreach ($order->getProducts() as $product) {
            $items[] = ['title' => (string) $product['product_name'], 'quantity' => max(1, (int) $product['product_quantity'])];
        }
        $payload['lineItems'] = array_slice($items, 0, 50);

        exit(json_encode(['found' => true, 'order' => $payload]));
    }
}

Register the URL (https://www.example.com/module/humindtracking/track) in the dashboard with authentication API key (header), header name X-Api-Key, and run the test with a real order.

The same logic applies to WooCommerce (a REST route under humind/v1, search by order number and compare billing_email) or any custom back office.

Test it yourself ​

Before wiring the dashboard, call your endpoint the way Humind does:

bash
curl -sS -X POST https://www.example.com/module/humindtracking/track \
  -H 'Content-Type: application/json' \
  -H 'X-Api-Key: <secret>' \
  -d '{"orderNumber":"XKBKNABJK","email":"jane@example.com"}'

Check three cases: a valid pair returns the order, a valid reference with a wrong email returns { "found": false }, and a missing or wrong API key returns 401.

Troubleshooting ​

Symptom in the test panelCause
"Custom tracking API is unreachable"DNS, firewall, TLS error, the endpoint took more than 8 seconds, or the URL redirects (module disabled, http to https, www).
"Custom tracking API returned HTTP 401" (or any non-200)Authentication mismatch, or your endpoint answers an error status instead of found: false.
"Custom tracking API returned invalid JSON"The response body is not JSON (HTML error page, PHP warning printed before the JSON).
"response does not match the expected contract"A required field is missing, a value is too long, an optional field is null, or quantity is 0. The panel shows only this generic message: check the field table.
"No order matches" although the order existsReference normalization (#, case) or email comparison on your side.

Released under the proprietary Humind license.