Skip to main content

Documentation Index

Fetch the complete documentation index at: https://terminal49.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

Carriers frequently revise arrival estimates as vessels encounter weather, port congestion, or schedule changes. The shipment.estimated.arrival event fires every time Terminal49 detects an ETA change so you can react immediately.

Events to subscribe to

EventWhen it fires
shipment.estimated.arrivalShipment-level ETA changes for the port of discharge
container.transport.estimated.vessel_arrivedContainer-level ETA changes for vessel arrival at the port of discharge
container.transport.estimated.arrived_at_inland_destinationContainer-level ETA changes for the inland destination (rail moves)

What the payload includes

When a shipment.estimated.arrival event fires, the included array contains:
  • An estimated_event object with the new estimated_timestamp
  • The full shipment object with updated pod_eta_at and related fields
  • The port object for the port of discharge
  • The vessel object when Terminal49 can identify the vessel

Handle the webhook

Parse the incoming notification, extract the new ETA, and compare it to your stored value:
app.post("/webhooks/terminal49", (req, res) => {
  const { data, included } = req.body;

  if (data.attributes.event !== "shipment.estimated.arrival") {
    return res.sendStatus(200);
  }

  const shipment = included.find((obj) => obj.type === "shipment");
  const estimatedEvent = included.find((obj) => obj.type === "estimated_event");

  const newEta = estimatedEvent.attributes.estimated_timestamp;
  const bolNumber = shipment.attributes.bill_of_lading_number;
  const portOfDischarge = shipment.attributes.port_of_discharge_name;

  // Compare against your stored ETA
  const previousEta = await getStoredEta(bolNumber);
  const changeInHours = Math.abs(
    (new Date(newEta) - new Date(previousEta)) / (1000 * 60 * 60)
  );

  if (changeInHours > 12) {
    await notifyTeam({
      message: `ETA for ${bolNumber} at ${portOfDischarge} shifted by ${Math.round(changeInHours)} hours`,
      newEta,
      previousEta,
    });
  }

  await updateStoredEta(bolNumber, newEta);

  res.sendStatus(200);
});
ETA events can fire multiple times per day as carriers update their schedules. Filter for significant changes (e.g., more than 12 hours) to avoid alert fatigue.

Inland destination ETAs

For shipments with an inland rail move, subscribe to container.transport.estimated.arrived_at_inland_destination as well. This event fires when the estimated arrival at the rail ramp or inland depot changes. This is a container transport event, so the payload includes a transport_event object rather than an estimated_event object. Look for the transport_event in included, then read attributes.timestamp for the estimated arrival time.

Container-level vessel arrival ETAs

Subscribe to container.transport.estimated.vessel_arrived when you need ETA changes at the container level. This event uses the same transport event payload shape as other container.transport.* events, with the estimated vessel arrival stored on the included transport_event object.

Common patterns

  • Threshold alerts — only notify when the ETA shifts by more than N hours
  • Direction tracking — distinguish delays (ETA moved later) from early arrivals (ETA moved earlier)
  • Customer notifications — forward ETA changes to your customers with a human-readable message
  • Planning updates — adjust warehouse receiving schedules or drayage bookings automatically