When to use this
Use CKO-03 direct delivery when:- The subscriber’s building is classified as first-line in kurnl and the session carries a resolvable
location_hash— the switch port is known (captive-portal flow), so kurnl can provision it directly - You run your own checkout page and want full control over the payment and signup experience
- Your plan is listed on the kurnl marketplace and you want subscribers to start there
How kurnl routes the callback
ForPOST /partner/external-checkout/complete with a session_id, the delivery_mode
field is ignored. kurnl resolves the session’s building_id to the building’s
line type — operator-side configuration that partners never set — and routes:
kurnl fails closed: only an explicit first-line building with a resolvable port
auto-provisions the switch. A session carrying neither a port nor a
unit_id is
rejected with 422 (see error handling).
delivery_mode still controls the routing only for legacy no-session payloads
(see the explicit-fields reference below).
How it works
Step-by-step
1. Configure your checkout URL
In the provider dashboard, open Checkout (/checkout-settings) and set the Store checkout URL. When a subscriber selects your plan, the kurnl Marketplace creates a checkout session and redirects them to your page with a single query parameter appended:
Example redirect:
2. Read the session
On page load, fetch the session to get the plan and location the subscriber selected:plan_versions object gives you the plan name and price to display. Reading the session here also protects it: from your first read on, it no longer expires, so a slow payment cannot cost the order. A session your page never reads expires 2 hours after it was created, and a 410 Gone then means the subscriber must restart from the Marketplace. See Checkout Sessions for the full session API, including the PATCH upsell endpoint for swapping plans mid-checkout.
Legacy handoff: integrations built before the session flow received
location_hash, plan_version_id, and service_provider_document_id as individual query parameters. That handoff is obsolete — new integrations should always read the session. The callback still accepts explicit fields for non-Marketplace flows (see the field reference below).3. Collect payment and subscriber details
Run your normal checkout flow. Collect the subscriber’s:- Name, email, phone (optional)
- Billing address
- Payment method
4. Call kurnl after successful payment
After payment succeeds, POST to/partner/external-checkout/complete with the session_id and the subscriber’s details. kurnl reads the plan, location, building, and provider from the session — you never re-send them:
job_id in the second shape — provisioning has not started; the subscription is
PENDING_INSTALL and kurnl fires subscription.pending_install instead of
subscription.activated. See home-drop for what happens next.
Store the subscription_id and subscriber_id in your system — you’ll need them for any future subscriber management calls. The callback is idempotent: a second call with the same session_id returns the existing subscription and the real subscriber_id, without job_id, with the message "Subscription already exists for this session_id (idempotent replay)".
5. Handle webhook events
kurnl fires events to your configuredwebhook_url at each stage:
At
provisioning.completed, the subscriber’s internet is live. This is the right moment to send them a confirmation email.
See Webhooks for signature verification and retry behaviour.
Field reference
Session flow (recommended)
Explicit fields (legacy, non-Marketplace flows only)
Without asession_id, routing is controlled by delivery_mode:
Subscriber fields (required in both flows)
Optional fields
Contact detail fields
Address fields
Error handling
410 body shape — unlike other errors, the
detail is a nested object:
{"detail": {"detail": "Session cancelled"}} for cancelled sessions — no expired_at.)
On 5xx errors: Safe to retry the full request. kurnl rolls back any partial state automatically on failure.
On network timeout: Retry the request — it is idempotent. The same session_id returns the existing subscription. The explicit-fields flow is idempotent too: a retry with the same subscriber email + plan_version_id + location returns the existing subscription with the message "Subscription already exists (idempotent replay)" (or "Provisioning already in progress" while the job is still running; job_id may be empty on replay).
Anonymous variant
If you own the end-user relationship and don’t want kurnl to store subscriber PII, use the anonymous variant instead. You provide anexternal_subscription_id (your own identifier) instead of a subscriber object. kurnl provisions the port but creates no subscriber record.