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:

  1. Return a 2xx status code quickly (within 10 seconds)
  2. Process the payload asynchronously if needed
  3. 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