Endpoint
Authentication
X-Webhook-Secret header — your pre-shared webhook secret.
Routing — session path (session_id present)
With a session_id, delivery_mode is ignored. kurnl resolves the session’s
building_id to the building’s line type (operator-side configuration) and routes:
A first-line building is reached two ways. From the captive portal the port is known
(
location_hash) and kurnl provisions it immediately. From address search or a QR
landing there is no port — only a building and a suite — so the order takes the
install path, exactly as a second- or third-line building does.
kurnl fails closed — a payload carrying neither a port nor a unit_id is rejected
with 422 (see error codes), and only an explicit first-line building with a
resolvable port ever provisions a switch unattended. The same rule now applies to
the no-session payload: delivery_mode says what you have, the building’s line
type decides the outcome.
Request body — session-based (recommended)
When the subscriber arrived via a kurnl Marketplace redirect, you have asession_id
(from the ?session_id= query parameter). The session is the source of truth for the
provider, plan, location/unit, building, and MAC address — you only supply the
subscriber PII:
location_hash or mac_address and they
disagree, the session value wins (the conflict is logged).
Idempotent: a second call with the same session_id returns the existing subscription
without re-provisioning — response shape {subscription_id, subscriber_id, message}
(no job_id), where subscriber_id is the real kurnl subscriber UUID.
Request body — explicit fields (legacy, no session)
For flows that do not go through a Marketplace session, pass the provisioning fields yourself.delivery_mode states which fields you are sending; the building’s line
type still decides whether the order provisions a port or raises an install job, so
a mismatch is refused with 422:
Building a storefront that owns its funnel? Prefer
POST /provisioning/provider-initiated with
your X-API-Key — it is the documented CKO-04 path, takes the same
delivery_mode / unit_id pair, and tells you which route was taken."Subscription already exists (idempotent replay)" or "Provisioning already in progress" — job_id may be empty on replay.
Response — 200 OK (provisioned — first-line building, or legacy direct)
subscription.activated, then provisioning.completed / provisioning.failed.
Response — 200 OK (install job — non-first-line building, or legacy home_drop)
job_id; subscription status is PENDING_INSTALL. Webhook:
subscription.pending_install (not subscription.activated).