Skip to main content
POST
/
v1
/
webhooks
Create Webhook
curl --request POST \
  --url https://api.shoal.xyz/v1/webhooks \
  --header 'Authorization: Bearer <token>'
Register a webhook URL to receive radar and/or signal events as they occur. Shoal will POST a JSON payload to your URL whenever a matching event is detected.

Request Body

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint to receive events. Must not point to a private/internal address.
event_typesstring[]YesArray of event types to subscribe to: radar, signal, or both.

Request

cURL
curl -X POST "https://api.shoal.xyz/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/shoal-webhook",
    "event_types": ["radar", "signal"]
  }'
Python
import os, requests

API_KEY = os.environ.get("SHOAL_API_KEY", "YOUR_API_KEY")

r = requests.post(
    "https://api.shoal.xyz/v1/webhooks",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "url": "https://example.com/shoal-webhook",
        "event_types": ["radar", "signal"],
    },
)
print(r.json())
JavaScript
const API_KEY = process.env.SHOAL_API_KEY || 'YOUR_API_KEY';

const res = await fetch('https://api.shoal.xyz/v1/webhooks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com/shoal-webhook',
    event_types: ['radar', 'signal'],
  }),
});
console.log(await res.json());

Response (201)

{
  "data": {
    "id": 12,
    "url": "https://example.com/shoal-webhook",
    "secret": "a1b2c3d4e5f6...64-char-hex-string",
    "event_types": ["radar", "signal"],
    "active": true,
    "created_at": "2026-03-10T12:00:00Z"
  }
}
Save the secret from the response — it is only returned once at creation time. You’ll need it to verify webhook signatures. See Webhook Signatures.

Errors

StatusErrorCause
400url is requiredMissing URL
400URL must use HTTPSNon-HTTPS URL
400URL must not point to a private addressURL resolves to localhost/private IP
400event_types must be a non-empty arrayMissing or empty event types
400event_types must only contain: radar, signalInvalid event type
400Maximum 5 webhooks allowedPer-account webhook limit reached