Webhooks
Receive real-time notifications when your forecasts are ready
Overview
Webhooks allow you to receive HTTP notifications when your SpotCast forecasts complete. Instead of polling the status endpoint, you can provide a webhook_url when creating a SpotCast and we'll send you a POST request when processing is done.
HTTPS Required
Webhook URLs must use HTTPS. HTTP endpoints are not supported for security reasons.
Setting Up Webhooks
Include a webhook_url when creating a SpotCast:
curl -X POST https://api.sealegs.ai/v3/spotcast \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"latitude": 25.7617,
"longitude": -80.1918,
"start_date": "2025-12-05T00:00:00Z",
"num_days": 3,
"webhook_url": "https://your-server.com/sealegs-webhook"
}'
Webhook Payload
When your SpotCast completes, we'll send a POST request to your webhook URL with the following payload:
Request Headers
POST /sealegs-webhook HTTP/1.1
Host: your-server.com
Content-Type: application/json
X-SeaLegs-Event: spotcast.forecast.completed
X-SeaLegs-Signature: sha256=abc123...
X-SeaLegs-Delivery-ID: whk_abc123xyz
X-SeaLegs-Timestamp: 1701432045
Completed Event
{
"event": "spotcast.forecast.completed",
"created_at": "2025-12-01T10:30:45Z",
"data": {
"spotcast_id": "spc_abc123xyz",
"forecast_id": "fcst_xyz789",
"status": "completed",
"recommendation": "GO",
"summary": "Excellent conditions expected. Light winds 8-12kt from the NE with calm 1-2ft seas.",
"coordinates": {
"latitude": 25.7617,
"longitude": -80.1918
},
"forecast_period": {
"start_date": "2025-12-05T00:00:00Z",
"end_date": "2025-12-08T00:00:00Z",
"num_days": 3
},
"links": {
"spotcast": "https://api.sealegs.ai/v3/spotcast/spc_abc123xyz"
}
}
}
Failed Event
{
"event": "spotcast.forecast.failed",
"created_at": "2025-12-01T10:30:45Z",
"data": {
"spotcast_id": "spc_abc123xyz",
"forecast_id": "fcst_xyz789",
"status": "failed",
"error": {
"code": "weather_data_unavailable",
"message": "Unable to fetch weather data for the specified location"
}
}
}
Event Types
| Event | Description |
|---|---|
spotcast.forecast.completed |
Forecast processing completed successfully |
spotcast.forecast.failed |
Forecast processing failed |
Verifying Signatures
To ensure webhook requests are from SeaLegs, verify the signature in the X-SeaLegs-Signature header. The signature is an HMAC-SHA256 hash of the request body using your webhook secret.
Coming Soon
Webhook secrets and signature verification will be available in a future update. For now, validate requests by checking the payload structure and your SpotCast IDs.
Verification Example (Python)
import hmac
import hashlib
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
"""Verify the webhook signature."""
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
expected_signature = f"sha256={expected}"
return hmac.compare_digest(expected_signature, signature)
# In your webhook handler:
@app.post("/sealegs-webhook")
async def handle_webhook(request: Request):
body = await request.body()
signature = request.headers.get("X-SeaLegs-Signature")
if not verify_webhook_signature(body, signature, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
data = json.loads(body)
# Process the webhook...
return {"status": "ok"}
Verification Example (JavaScript)
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const expectedSignature = `sha256=${expected}`;
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signature)
);
}
// In your Express handler:
app.post('/sealegs-webhook', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-sealegs-signature'];
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const data = JSON.parse(req.body);
// Process the webhook...
res.json({ status: 'ok' });
});
Responding to Webhooks
Your webhook endpoint should:
- Return a
2xxstatus code quickly (within 10 seconds) - Process the payload asynchronously if needed
- Handle duplicate deliveries idempotently
Respond Quickly
Return a 200 response as soon as you've received the webhook. Process the data asynchronously to avoid timeouts.
Retry Policy
If your webhook endpoint fails to respond with a 2xx status code, we'll retry the delivery:
| Attempt | Delay |
|---|---|
| 1st retry | 5 minutes |
| 2nd retry | 30 minutes |
| 3rd retry | 2 hours |
| 4th retry | 24 hours |
After 5 failed attempts, the webhook is marked as failed and no further retries are attempted.
Best Practices
Respond Quickly
Return a 200 response immediately and process the webhook asynchronously.
Handle Duplicates
Use the delivery ID to deduplicate. The same webhook may be delivered multiple times.
Verify Signatures
Always verify the signature to ensure the request is from SeaLegs.
Use HTTPS
Webhook URLs must use HTTPS for secure delivery.
Testing Webhooks
During development, you can use services like webhook.site or ngrok to test webhook deliveries to your local development environment.
Using ngrok
# Start ngrok tunnel to your local server
ngrok http 3000
# Use the HTTPS URL provided by ngrok as your webhook_url
# https://abc123.ngrok.io/sealegs-webhook