Notifications API
Subscribe to real-time vessel events via webhooks or WebSocket. Get notified instantly when vessels arrive at ports, change course, enter geofences, or update their ETA -- without polling the API.
Overview
The Notifications API lets you create event-driven subscriptions for the vessels you care about. Instead of repeatedly polling endpoints, you define a notification configuration specifying which vessels to watch, which event types to listen for, and where to deliver events. VesselAPI then pushes matching events to your webhook URL or WebSocket connection in real time.
This is ideal for building alerting systems, operational dashboards, supply chain triggers, and any workflow that needs to react immediately to changes in vessel activity.
Subscription Note: The Notifications API is available on paid plans (Basic, Starter, and Pro). View pricing for details.
Quick Start
Get your API key
Sign up at vesselapi.com/pricing and grab your Bearer token from the dashboard.
Create a notification configuration
POST to /notifications with the vessels to watch, event types, and your webhook URL.
Receive events
VesselAPI pushes JSON event envelopes to your webhook. Verify signatures, process events, and respond with a 2xx status.
Event Types
Events are organized into four categories. Subscribe to individual event types or all events in a category.
| Category | Event Type | Description |
|---|---|---|
| Port Events | port.arrival | Vessel has entered a port area |
| port.departure | Vessel has left a port area | |
| ETA / Voyage Changes | eta.destination_changed | Vessel has changed its reported destination |
| eta.eta_changed | ETA to destination has shifted beyond the configured threshold | |
| eta.draught_changed | Vessel draught reading has changed (loading/unloading indicator) | |
| Position Changes | position.nav_status_changed | AIS navigational status has changed (e.g., underway to anchored) |
| position.speed_changed | Speed over ground changed beyond the configured threshold | |
| position.update | Generic — fires on every new AIS position report received for the vessel, regardless of whether the position actually changed. Use this for continuous tracking or heartbeat monitoring. | |
| position.position_changed | Specific — fires only when the vessel's actual geographic coordinates have changed. Filters out duplicate or stationary position reports, so you only get notified when the vessel moves. | |
| Geofence | position.geofence_enter | Vessel has entered a user-defined geofence area |
| position.geofence_exit | Vessel has left a user-defined geofence area |
position.update vs position.position_changed
These two event types serve different use cases. position.update is a generic event that fires on every AIS position report received — even if the vessel hasn't moved. This is useful for heartbeat monitoring or building a complete position timeline. position.position_changed is a specific event that only fires when the vessel's actual geographic coordinates change, filtering out duplicate or stationary reports. Use this when you only care about actual movement.
Delivery Methods
Webhooks
Webhooks are the primary delivery method. When an event matches your notification configuration, VesselAPI sends an HTTP POST request to your configured webhookUrl with the event payload as JSON.
- Your endpoint must respond with a
2xxstatus within 10 seconds. - Failed deliveries are retried up to 3 times with exponential backoff (1s, 2s, 4s).
- After all retries are exhausted, the event is dropped.
HMAC Signature Verification
Every webhook request includes an X-Signature-256 header containing an HMAC-SHA256 digest of the request body, signed with your webhookSecret. Always verify this signature to confirm events originate from VesselAPI.
import hmac
import hashlib
def verify_signature(payload_body: bytes, secret: str, signature: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
WebSocket
For low-latency or real-time dashboard use cases, you can enable WebSocket delivery by setting websocket: true in your notification configuration. WebSocket can be used alongside or instead of webhooks.
Connection
Connect to the WebSocket endpoint with your notification ID:
wss://api.vesselapi.com/v1/ws?notification_id=YOUR_NOTIFICATION_ID
Authentication is done via the Authorization header during the HTTP upgrade request, using the same Bearer token as the REST API.
| Detail | Value |
|---|---|
| Message format | JSON text frames — same EventEnvelope structure as webhooks |
| Keepalive | Server sends ping every 30 seconds. Respond with pong within 10 seconds or the connection closes. |
| Connection limit | One connection per notification. Opening a new connection for the same notification replaces the previous one. |
| Reconnection | Use exponential backoff. Events during disconnection are not replayed. |
Browser note: The browser WebSocket API does not support custom headers. For browser clients, consider proxying through your backend. For server-side clients, use a library that supports headers (e.g., ws for Node.js, websockets for Python).
WebSocket Client (Node.js)
import WebSocket from 'ws';
const ws = new WebSocket(
'wss://api.vesselapi.com/v1/ws?notification_id=YOUR_NOTIFICATION_ID',
{ headers: { Authorization: 'Bearer YOUR_API_KEY' } }
);
ws.on('open', () => console.log('Connected to event stream'));
ws.on('message', (data) => {
const envelope = JSON.parse(data);
const event = envelope.event;
console.log(`[${event.type}] ${event.vessel.vesselName}`, event.data);
});
ws.on('close', (code, reason) => {
console.log(`Disconnected: ${code} ${reason}`);
// Reconnect with exponential backoff
});
ws.on('error', (err) => console.error('WebSocket error:', err));
WebSocket Client (Python)
import asyncio
import json
import websockets
async def listen():
uri = "wss://api.vesselapi.com/v1/ws?notification_id=YOUR_NOTIFICATION_ID"
headers = {"Authorization": "Bearer YOUR_API_KEY"}
async with websockets.connect(uri, additional_headers=headers) as ws:
print("Connected to event stream")
async for message in ws:
envelope = json.loads(message)
event = envelope["event"]
print(f"[{event['type']}] {event['vessel']['vesselName']}")
asyncio.run(listen())
Notification Configuration
A notification configuration defines what to watch and where to deliver events. The following fields are available when creating or updating a configuration.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Human-readable label for this configuration |
| imos | integer[] | * | List of IMO numbers to watch (max 100 combined with mmsis) |
| mmsis | integer[] | * | List of MMSI numbers to watch (max 100 combined with imos) |
| eventTypes | string[] | Yes | Event types to subscribe to (see table above) |
| webhookUrl | string | ** | HTTPS URL to receive event payloads |
| webhookSecret | string | Conditional | HMAC-SHA256 secret for signing webhook payloads. Required when webhookUrl is set. |
| websocket | boolean | No | Enable WebSocket delivery (default: false) |
| geofence | object | No | Bounding box defining a custom geofence area (lonLeft, lonRight, latBottom, latTop) |
| speedThresholdKnots | number | No | Minimum speed change (in knots) to trigger position.speed_changed |
| etaShiftThresholdMinutes | number | No | Minimum ETA shift (in minutes) to trigger eta.eta_changed |
* At least one of imos or mmsis is required. ** At least one delivery method (webhookUrl or websocket: true) is required.
Configuration Examples
The difference between delivery channels is simply which fields you set. Use webhookUrl + webhookSecret for webhook delivery, websocket: true for WebSocket delivery, or set both on the same notification to receive events on both channels simultaneously.
Webhook Only
curl -X POST "https://api.vesselapi.com/v1/notifications" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Fleet alerts",
"imos": [9321483, 9472440],
"webhookUrl": "https://example.com/webhook",
"webhookSecret": "my-hmac-secret",
"eventTypes": ["port.arrival", "port.departure", "position.update"]
}'
WebSocket Only
curl -X POST "https://api.vesselapi.com/v1/notifications" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Live tracking",
"imos": [9321483, 9472440],
"websocket": true,
"eventTypes": ["position.position_changed"]
}'
Then connect to the WebSocket endpoint with the returned notification ID:
wss://api.vesselapi.com/v1/ws?notification_id=<id>
# With header: Authorization: Bearer YOUR_API_KEY
API Quota: Every delivered event counts as 1 API call against your monthly quota, regardless of delivery channel. If both webhook and WebSocket are enabled on the same notification, the same event delivered to both channels still counts as 1 call, not 2.
API Endpoints
/notifications
Create a new notification configuration.
/notifications
List all notification configurations for your account.
/notifications/{id}
Retrieve a single notification configuration by ID.
/notifications/{id}
Update an existing notification configuration.
/notifications/{id}
Delete a notification configuration.
/notifications/{id}/test
Send a test event to verify your webhook or WebSocket is receiving events correctly.
Authentication: All endpoints require a Bearer token in the Authorization header, the same API key used across the VesselAPI.
Event Payload
Every event is delivered as a JSON envelope containing the event type, vessel information, a timestamp, and typed data specific to the event. Here is an example port.arrival event:
{
"object": "event",
"event": {
"id": "94ff8b0d-08d8-466c-b37a-c024117b8880",
"type": "port.arrival",
"timestamp": "2026-03-27T08:14:32Z",
"detectedAt": "2026-03-27T08:15:00Z",
"vessel": {
"imo": 9811000,
"mmsi": 353136000,
"vesselName": "EVER GIVEN"
},
"data": {
"portEvent": {
"event": "Arrival",
"timestamp": "2026-03-27T08:14:32Z",
"vessel": { "name": "EVER GIVEN", "imo": 9811000, "mmsi": 353136000 },
"port": { "name": "Rotterdam", "country": "Netherlands", "unlo_code": "NLRTM" }
},
"portCall": {}
},
"notificationId": "550e8400-e29b-41d4-a716-446655440000"
}
}
| Field | Description |
|---|---|
| object | Always "event" (envelope discriminator) |
| event.id | Unique event identifier (idempotency key) |
| event.type | One of the event type strings listed above |
| event.timestamp | ISO 8601 UTC timestamp when the event occurred |
| event.detectedAt | ISO 8601 UTC timestamp when VesselAPI detected the event |
| event.vessel | Object with imo, mmsi, and vesselName |
| event.data | Event-specific payload (port info, position, ETA details, etc.) |
| event.notificationId | ID of the notification configuration that matched this event |
Code Examples
Create a Notification (cURL)
curl -X POST "https://api.vesselapi.com/v1/notifications" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ever Given Port Alerts",
"imos": ["9811000"],
"eventTypes": ["port.arrival", "port.departure"],
"webhookUrl": "https://your-app.com/webhooks/vessel-events",
"webhookSecret": "whsec_your_secret_key"
}'
Create a Notification (TypeScript SDK)
import { VesselAPI } from "@vesselapi/sdk";
const vessel = new VesselAPI({ apiKey: "YOUR_API_KEY" });
const notification = await vessel.notifications.create({
name: "Ever Given Port Alerts",
imos: ["9811000"],
eventTypes: ["port.arrival", "port.departure"],
webhookUrl: "https://your-app.com/webhooks/vessel-events",
webhookSecret: "whsec_your_secret_key",
});
console.log(`Created notification: ${notification.id}`);
Create a Notification (Python SDK)
from vesselapi import VesselAPI
client = VesselAPI(api_key="YOUR_API_KEY")
notification = client.notifications.create(
name="Ever Given Port Alerts",
imos=["9811000"],
event_types=["port.arrival", "port.departure"],
webhook_url="https://your-app.com/webhooks/vessel-events",
webhook_secret="whsec_your_secret_key",
)
print(f"Created notification: {notification.id}")
Handle Incoming Webhook (Python / Flask)
import hmac, hashlib, json
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_key"
@app.route("/webhooks/vessel-events", methods=["POST"])
def handle_event():
# Verify signature
signature = request.headers.get("X-Signature-256", "")
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(), request.data, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401)
envelope = request.json
event = envelope["event"]
event_type = event["type"]
if event_type == "port.arrival":
port = event["data"]["portEvent"]["port"]["name"]
vessel = event["vessel"]["vesselName"]
print(f"{vessel} arrived at {port}")
return "", 200
Try It in the API Explorer
For full endpoint details, request/response schemas, and live testing, open the interactive API Explorer.