See the full guides: CKO-03 Direct Delivery and CKO-03 Home-Drop Delivery.

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. When the subscriber arrived via a kurnl Marketplace redirect, you have a session_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:
If both the session and your payload carry a 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.
This flow is idempotent per (subscriber email, plan version, location): a retry returns the existing subscription with "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)

Webhook: subscription.activated, then provisioning.completed / provisioning.failed.

Response — 200 OK (install job — non-first-line building, or legacy home_drop)

No job_id; subscription status is PENDING_INSTALL. Webhook: subscription.pending_install (not subscription.activated).
The response shape varies with the routing (there is no fixed response model) — always parse job_id as optional and branch on its presence.

Error codes