Solutions · Port Calls API

Arrivals, departures and time in port

Every stop a vessel makes in a port, derived from its AIS positions: when it arrived, when it left and how long it stayed. Filter by port or vessel, and get a webhook for each arrival and departure.

  • Proof of delivery and ETA updates for shippers
  • Port-call history for chartering
  • Demurrage and laytime tracking
Request
# Calls by port, mmsi, status (in_port or departed) and since
curl "https://seasignals.com/v1/port-calls?port=NLRTM&status=in_port" \
  -H "Authorization: Bearer $SS_KEY"
Endpoints

Query it whenever you need it

GeoJSON responses with typed IDs, ISO 8601 times and units in every field name. Errors come back as {"error": {"code", "message"}}.

GET/v1/port-callsCalls by port, mmsi, status (in_port or departed) and since
GET/v1/portsPorts with UN/LOCODE ids and whether they're in live coverage
Webhooks

Or let the events come to you

Subscribe a URL once and SeaSignals POSTs a signed JSON event whenever something changes. Sent within about 10 minutes of the vessel stopping or leaving.

EVENTport_call.arrivedA vessel stopped inside a watched port
EVENTport_call.departedIt left the port, with its total stay

Filters

portsUN/LOCODEs, e.g. ["NLRTM", "BEANR"]; all ports when left out
mmsiOnly these vessels
Create a webhook
curl -X POST https://seasignals.com/v1/webhooks \
  -H "Authorization: Bearer $SS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/hooks/seasignals",
  "product": "port_calls",
  "events": [
    "port_call.arrived",
    "port_call.departed"
  ],
  "filters": {
    "ports": [
      "NLRTM",
      "BEANR"
    ]
  }
}'
POST to your URL
{
  "id": "evt_3kV9q2mXw8rT1yLp0aZc4nB7",
  "type": "port_call.departed",
  "created_at": "2026-10-29T09:00:12Z",
  "webhook": "webhook:42",
  "data": {
    "id": "port_call:8812",
    "status": "departed",
    "port": {
      "id": "port:NLRTM",
      "name": "Rotterdam",
      "country": "NL"
    },
    "vessel": {
      "id": "vessel:mmsi:244123456",
      "mmsi": 244123456,
      "name": "NORDIC STAR",
      "type": "cargo"
    },
    "arrived_at": "2026-10-27T22:10:00Z",
    "departed_at": "2026-10-29T06:40:00Z",
    "stay_hours": 32.5
  }
}
Delivery

Signed, retried, logged

  • Signed: every request carries SeaSignals-Signature, an HMAC-SHA256 of the timestamp and body with your webhook secret.
  • Retried: anything but a 2xx within 10 seconds is retried after 1 min, 5 min, 30 min, 2 h and 6 h.
  • Logged: GET /v1/webhooks/{id} shows recent deliveries, and POST /v1/webhooks/{id}/test sends a test event.
  • Safe to retry: use the event id to ignore a delivery you've already handled.
// header: SeaSignals-Signature: t=1761729600,v1=5f2b…
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto.createHmac("sha256", process.env.SS_WEBHOOK_SECRET)
  .update(`${t}.${rawBody}`).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
  && Date.now() / 1000 - Number(t) < 300;

Try the Port Calls API free

The free trial includes every API and up to 5 webhooks, 1,000 requests a day and 14 days of history.