POST /v2/webhooks
Register a webhook
Registers a URL that Nuton will POST to whenever an event you’ve subscribed to fires. Webhooks are scoped to your tenant — they receive events for every user under your API key.
Request
Headers
| Header | Required | Notes |
|---|---|---|
X-API-Key | yes | |
Content-Type | yes | application/json |
Note: Webhook registration is a tenant-level operation — no
X-External-User-Idneeded.
Body
url string (URL) required HTTPS endpoint Nuton will POST events to. Max 2048 characters. |
events string[] default ["course.completed", "course.failed", "job.progress"] Event types to subscribe to. See Event types. |
Returns
{
"id": "wh_01HW0XYZ…",
"url": "https://example.com/hooks/nuton",
"events": ["course.completed", "course.failed"],
"active": true,
"secret": "whsec_2YmW…dXk",
"created_at": "2026-05-19T14:11:00Z"
}
Store secret immediately. It’s the only time it’s returned. Use it to verify the X-Nuton-Signature header on every incoming webhook delivery (HMAC-SHA256 of the raw request body).
Verifying deliveries
Every webhook POST from Nuton includes:
Content-Type: application/jsonX-Nuton-Event: <event_type>— the event name (e.g.course.completed)X-Nuton-Signature: <hex>— HMAC-SHA256 of the raw body, using your webhook’ssecret
Example verification in Python:
import hmac, hashlib
def verify(body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
Errors
- 422 —
urlis not HTTPS, oreventscontains an unknown event type.