FreightCake
Walkthroughs

Track a shipment

Surface live tracking status to your users via polling or webhooks.

Once a shipment is booked, you have two ways to keep your UI in sync with the carrier’s state: polling or webhooks. Use webhooks for production — polling is fine for prototypes.

Polling

For low-volume applications, poll the tracking endpoint every few minutes:

import { fc } from './freightcake'

const tracking = await fc.freight.getTracking('9001')

console.log(`Status: ${tracking.status}`)
console.log(`PRO number: ${tracking.pro_number ?? 'pending'}`)
console.log(`Location: ${tracking.location ?? 'not reported'}`)

Response shape

{
  "object": "tracking",
  "shipment_id": 9001,
  "pro_number": "SBFC0009001",
  "carrier": "Sandbox Freight Co",
  "status": "IN_TRANSIT",
  "location": "Kansas City, MO",
  "last_checked_at": "2026-07-10T18:14:00.000Z"
}

Use the standalone tracking resource when you only have a PRO number and no FreightCake shipment ID.

Polling cadence

Don’t hammer the tracking endpoint. Recommended cadence:

StatusPoll interval
scheduledEvery 30 minutes
picked_up, in_transitEvery 15 minutes
out_for_deliveryEvery 5 minutes
delivered, exception, cancelledStop polling

A polling loop that respects this:

function normalizeTrackingStatus(status: string | null) {
  return status?.trim().toLowerCase().replace(/[\s-]+/g, '_') ?? ''
}

async function pollUntilDone(shipmentId: number, signal: AbortSignal) {
  const intervals: Record<string, number> = {
    scheduled: 30 * 60_000,
    picked_up: 15 * 60_000,
    in_transit: 15 * 60_000,
    out_for_delivery: 5 * 60_000,
  }

  while (!signal.aborted) {
    const t = await fc.freight.getTracking(String(shipmentId))
    onUpdate(t)
    const status = normalizeTrackingStatus(t.status)
    if (['delivered', 'exception', 'cancelled', 'canceled'].includes(status)) return
    const wait = intervals[status] ?? 15 * 60_000
    await new Promise((r) => setTimeout(r, wait))
  }
}

Tracking status values can differ in casing and separators by source. Normalize them before comparisons, and fetch current state rather than treating an unknown value as terminal.

For production traffic, register a webhook endpoint and let FreightCake push status changes:

const endpoint = await fc.webhooks.create({
  url: 'https://your-app.example.com/freightcake/webhooks',
  events: [
    'shipment.picked_up',
    'shipment.in_transit',
    'shipment.out_for_delivery',
    'shipment.exception',
    'shipment.delivered',
  ],
})

await saveSecret(endpoint.secret)

You’ll receive secret only in the create response. Store it in your secret manager. Every webhook delivery is signed; verify it server-side:

import { verifyWebhookSignature } from '@freightcake/sdk'

export async function POST(req: Request) {
  const body = await req.text()
  const signature = req.headers.get('x-freightcake-signature')
  if (!signature) return new Response('Missing signature', { status: 401 })

  try {
    verifyWebhookSignature(body, signature, process.env.FREIGHTCAKE_WEBHOOK_SECRET!)
  } catch {
    return new Response('Invalid signature', { status: 401 })
  }

  const event = JSON.parse(body)
  switch (event.type) {
    case 'shipment.picked_up':
    case 'shipment.in_transit':
    case 'shipment.out_for_delivery':
      await onStatusChanged(event.data)
      break
    case 'shipment.exception':
      await onException(event.data)
      break
    case 'shipment.delivered':
      await onDelivered(event.data)
      break
  }

  // Always 200 quickly — process asynchronously.
  return new Response('ok')
}

See Webhooks for delivery guarantees, retry policy, and signature details.

Handling exceptions

A shipment goes to exception status when the carrier reports a problem (refused delivery, address correction, weather delay, damage). The webhook payload includes the carrier’s exception reason and a recommended action:

{
  "id": "evt_0192fbe3",
  "type": "shipment.exception",
  "created": "2026-07-10T18:14:00.000Z",
  "data": {
    "shipment": {
      "id": 9001,
      "proNumber": "SBFC0009001",
      "status": "EXCEPTION",
      "location": "Kansas City, MO",
      "carrier": "Sandbox Freight Co"
    }
  },
  "webhook": {
    "id": "whk_12",
    "url": "https://your-app.example.com/freightcake/webhooks"
  }
}

Fetch the latest freight tracking state for carrier-specific context, then surface the exception to your operations team.

Common gotchas

  • Webhook ordering is not guaranteed. The same shipment can deliver two events out of order. Compare created, then fetch current tracking state before mutating your state machine.
  • Test mode emits webhooks. A booking made with a fk_test_ key fires the same webhooks (mocked) so you can develop the receiver end to end.
  • Webhooks retry for about 38 hours. Return any 2xx within 10 seconds. Failed attempts back off from 1 minute through 24 hours; an endpoint is disabled and its owner is emailed after 10 consecutive failed attempts.

Next steps

On this page