Solutions · Storms API
Tropical cyclones with cones, tracks and approach alerts
Every active tropical cyclone worldwide with its position, intensity, forecast cone and wind zones. Get a webhook when a storm forms, strengthens, comes within range of your port or asset, or dissipates.
- Port and terminal hurricane preparedness
- Fleet and cargo exposure alerts
- Offshore operations planning
# Active systems with forecast cones, tracks and wind zones curl "https://www.seasignals.com/v1/storms/active?geometry=false" \ -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/storms/activeActive systems with forecast cones, tracks and wind zonesGET
/v1/ocean/pointExposure for a location: distance, bearing and whether it's in the cone (layers=storms)
Webhooks
Or let the events come to you
Subscribe a URL once and SeaSignals POSTs a signed JSON event whenever something changes. Checked every 30 minutes.
EVENT
storm.formedA new storm, at or above min_wind_knEVENT
storm.strengthenedIts category or alert level went upEVENT
storm.approachingIt came within radius_nm of your pointEVENT
storm.dissipatedIt's no longer activeFilters
lat | Your point, needed for storm.approaching |
lon | |
radius_nm | 1 to 1,000 nm |
min_wind_kn | Ignore weaker systems |
curl -X POST https://www.seasignals.com/v1/webhooks \ -H "Authorization: Bearer $SS_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/seasignals", "product": "storms", "events": [ "storm.formed", "storm.strengthened", "storm.approaching", "storm.dissipated" ], "filters": { "lat": 29.7, "lon": -95.0, "radius_nm": 400, "min_wind_kn": 34 } }'
{
"id": "evt_3kV9q2mXw8rT1yLp0aZc4nB7",
"type": "storm.approaching",
"created_at": "2026-10-29T09:00:12Z",
"webhook": "webhook:42",
"data": {
"type": "Feature",
"id": "storm:1001234",
"geometry": {
"type": "Point",
"coordinates": [
-90.4,
26.1
]
},
"properties": {
"name": "Vance",
"classification": "Hurricane",
"category": 2,
"max_wind_kn": 90,
"movement_dir_deg": 315,
"movement_speed_kn": 11,
"distance_nm": 318.6,
"bearing_deg": 128
}
}
}
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, andPOST /v1/webhooks/{id}/testsends a test event. - Safe to retry: use the event
idto 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;
import hashlib, hmac, os, time t, v1 = (part.split("=")[1] for part in header.split(",")) expected = hmac.new(os.environ["SS_WEBHOOK_SECRET"].encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest() ok = hmac.compare_digest(v1, expected) and time.time() - int(t) < 300
t, v1 = header.split(",").map { _1.split("=").last } expected = OpenSSL::HMAC.hexdigest("SHA256", ENV["SS_WEBHOOK_SECRET"], "#{t}.#{raw_body}") ok = ActiveSupport::SecurityUtils.secure_compare(v1, expected) && Time.now.to_i - t.to_i < 300
Try the Storms API free
The free trial includes every API and up to 5 webhooks, 1,000 requests a day and 14 days of history.