Documentation

Tracking

Live job status and agent location for the customer-facing track page.

Tracking is what powers the "where's my driver" view on a customer's order page. It combines two data sources: the job's status transitions (from the driver hitting /start-pickup, /arrive-dropoff, etc.) and the driver's live location heartbeat. The customer endpoint returns both in a single shape.

Endpoints

GET/client/logistics/orders/:jobId/trackJWT
PUT/client/logistics/jobs/:jobId/trackingJWT
PUT/logistics/delivery/jobs/:jobId/trackingJWT

Customer track view

const track = await fetch(`/client/logistics/orders/${jobId}/track`, {
  headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}` },
}).then(r => r.json());

// {
//   jobId, status: 'dropoff_in_progress',
//   stops: [
//     { type: 'pickup',  location: {...}, status: 'completed', completedAt: '...' },
//     { type: 'dropoff', location: {...}, status: 'in_progress', etaMinutes: 8 }
//   ],
//   agent: {
//     id, firstName, vehicleType, vehiclePlate,
//     location: { lat, lng, heading, updatedAt },
//     phone: '+1 555 0123',     // masked
//     rating: 4.8,
//   },
//   timeline: [
//     { event: 'job_created',     at: '...' },
//     { event: 'agent_assigned',  at: '...' },
//     { event: 'pickup_started',  at: '...' },
//     ...
//   ]
// }

The endpoint is auth-gated by the customer JWT — only the customer who placed the job can read it. To support guest tracking (no account), generate a signed track URL containing a one-off token; the front-end exchanges it on load and caches the JWT for the page.

Polling vs WebSocket

Out of the box, tracking is poll-based — the customer page hits /track every 10–15 seconds. There is no dedicated WebSocket gateway for delivery tracking in the current build; if you need push updates, listen to the delivery_job.updated event in the Automation module and forward the changes you care about over your own WS layer.

Updating tracking

The driver's app pushes per-job tracking updates with notes and ETA:

await fetch(`/client/logistics/jobs/${jobId}/tracking`, {
  method: 'PUT',
  headers: {
    orgid: 'my-org',
    Authorization: `Bearer ${jwt}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    status: 'pickup_in_progress',
    note: 'Picking up package',
    etaMinutes: 12,
    location: { lat: 40.7128, lng: -74.006 },
  }),
});

The shape merges into data.tracking on the job and the timeline entry is appended automatically. Most apps don't need to call this directly — the /start-pickup, /complete-pickup, etc. endpoints do it as a side effect.

Status events

Each status transition emits a domain event the rest of AppEngine can subscribe to:

EventWhenTypical use
delivery_job.createdJob createdTrigger autoassignment automation
delivery_job.assignedAgent picked or auto-assignedNotify customer "your driver is on the way"
delivery_job.pickup_startedDriver heading to pickupUpdate merchant dashboard
delivery_job.pickup_arrivedDriver at pickup pointNotify pickup contact
delivery_job.pickup_completedPackage collected"Out for delivery" customer email
delivery_job.dropoff_completedPOD capturedReceipt, review request
delivery_job.cancelled / failedTerminal failuresRefund, retry workflows

Hook these from the Automation module to fan out emails, SMS, or webhooks without writing code.

Carrier webhooks

For orgs running a hybrid model — own fleet plus parcel carrier — the Storefront's shipping integration handles carrier (UPS / FedEx / USPS / DHL) tracking webhooks under /storefront/shipping/webhook/*. Those webhooks update the order's shipment record with carrier status; they do not touch a Logistics delivery_job. Don't try to merge the two — they describe different work units.

Estimating ETA

The etaMinutes field is computed at quote time and updated as the driver moves. The default model is straight-line distance from the driver's current location to the next stop divided by an average speed for the vehicle type. For higher accuracy, plug a routing service into the DeliveryService.getRouteDistance extension point — see the source under /Users/imzee/projects/appengine/src/logistics/delivery.service.ts.

Tracking endpoints return the agent's masked phone number — never the raw line. If you need direct contact, use the in-job messaging endpoints (POST /client/logistics/jobs/:jobId/messages) which proxy through AppEngine without exposing personal numbers.