ShipEngine Tracking

Internal setup notes for ShipEngine package tracking and tracking update webhooks.

ENV Webhook Purchase Orders

The ShipEngine integration starts package tracking when a purchase order enters the Shipped state and receives tracking status updates from ShipEngine webhooks.

The implementation lives in src/Modules/ShipEngine.

What The Integration Does

Tempr listens for purchase order state changes. When a PurchaseOrder transitions to Shipped, the ShipEngine listener:

  1. Reads tracking numbers from purchase_orders.data.shipping.
  2. Creates or reuses a shipengine_packages row for each tracking number.
  3. Calls ShipEngine's tracking subscription endpoint.
  4. Records started_tracking_at when ShipEngine accepts the subscription.
  5. Logs and skips packages that are missing tracking numbers or cannot be subscribed.

ShipEngine sends tracking events to:

POST /integrations/shipengine/webhooks/track

When the webhook is accepted, Tempr queues HandleTrackingWebhook, then updates the matching shipengine_packages row with the latest carrier status, tracking URL, delivery dates, event timestamp, and raw webhook payload.

Required Environment Variables

Add these values to .env.

SHIPENGINE_API_KEY=
SHIPENGINE_BASE_URL=https://api.shipengine.com
SHIPENGINE_TIMEOUT=10
SHIPENGINE_RETRY_TIMES=2
SHIPENGINE_RETRY_SLEEP=250
SHIPENGINE_QUEUE=default

SHIPENGINE_WEBHOOK_URL=https://your-public-host.example.com/integrations/shipengine/webhooks/track
SHIPENGINE_WEBHOOK_SECRET=
SHIPENGINE_WEBHOOK_SECRET_HEADER=X-ShipEngine-Webhook-Secret

Notes On Env Values

SHIPENGINE_API_KEY is required for all outbound ShipEngine calls.

SHIPENGINE_WEBHOOK_URL must be publicly reachable by ShipEngine over HTTPS. For local testing, use the active tunnel URL.

SHIPENGINE_WEBHOOK_SECRET is optional but recommended. When set, Tempr expects ShipEngine to send the configured secret in the header named by SHIPENGINE_WEBHOOK_SECRET_HEADER.

SHIPENGINE_QUEUE controls where webhook processing jobs are dispatched.

After changing .env, clear cached config:

php artisan optimize:clear

ShipEngine Webhook Setup

Create a ShipEngine webhook for the track event. The target URL should be:

https://your-public-host.example.com/integrations/shipengine/webhooks/track

If using webhook secret validation, add a custom ShipEngine webhook header:

HeaderValue
X-ShipEngine-Webhook-SecretThe value of SHIPENGINE_WEBHOOK_SECRET

The service also exposes a helper that can create a track webhook through the ShipEngine API:

php artisan tinker
app(\TemprApp\Server\Modules\ShipEngine\Services\ShipEngineService::class)
    ->createTrackWebhook();

Use the ShipEngine dashboard or listWebhooks() to confirm the webhook exists.

Database

Run migrations after deploying the module:

php artisan migrate

The integration stores package tracking state in shipengine_packages.

Important fields:

  • purchase_order_id
  • carrier_code
  • tracking_number
  • tracking_url
  • status_code
  • status_detail_code
  • status_description
  • status_detail_description
  • estimated_delivery_date
  • actual_delivery_date
  • started_tracking_at
  • last_event_at
  • last_payload

carrier_code and tracking_number are unique together.

Purchase Order Data Contract

The shipped action writes tracking details to:

{
  "shipping": {
    "tracking_numbers": ["1Z999AA10123456784"],
    "method": "UPS",
    "cost": "12.50"
  }
}

The listener also accepts package-specific payloads at data.shipping.packages:

{
  "shipping": {
    "packages": [
      {
        "carrier_code": "ups",
        "tracking_number": "1Z999AA10123456784"
      }
    ]
  }
}

Carrier labels entered by users are normalized for common carriers such as UPS, FedEx, USPS, and DHL.

Runtime Flow

Shipped State

  1. A user marks a purchase order as shipped.
  2. Tempr saves the shipping method, cost, and tracking numbers into the purchase order data payload.
  3. The purchase order transitions to App\States\Orders\Shipped.
  4. StartTrackingPurchaseOrderPackages receives the state-change event.
  5. ShipEngine tracking is started for each tracking number.

Tracking Webhook

  1. ShipEngine posts a tracking event to the public webhook URL.
  2. ShipEngineTrackingWebhookController validates the optional secret header.
  3. Tempr accepts only resource_type=API_TRACK payloads.
  4. HandleTrackingWebhook updates the matching shipengine_packages row.

Verification

Confirm the route exists:

php artisan route:list --path=shipengine

Expected route:

POST integrations/shipengine/webhooks/track

Run focused tests:

php artisan test tests/Feature/ShipEngine/ShipEngineServiceTest.php

Troubleshooting

If tracking does not start, check:

  • The purchase order has tracking numbers in data.shipping.tracking_numbers.
  • SHIPENGINE_API_KEY is present and config cache has been cleared.
  • The queue worker is running if SHIPENGINE_QUEUE is not processed synchronously.
  • The carrier code is supported by ShipEngine for the tracking number.

If webhook updates do not arrive, check:

  • ShipEngine has a track webhook pointed at the current public URL.
  • The webhook secret header in ShipEngine matches Tempr's configured header and value.
  • The payload has resource_type=API_TRACK.
  • shipengine_packages contains the tracking number.