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
If the building is not first-line — or it is first-line but the session carries a suite rather than a port, which is what address search and the QR landing produce — the same callback creates an operator install job instead. See CKO-03 home-drop delivery.

How kurnl routes the callback

For POST /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).
Because the line type is operator configuration, your integration cannot know in advance which response it will get. Treat job_id as optional: if it is present, provisioning has started; if it is absent, the subscription is pending installation. Handle 422 gracefully by surfacing the error to support rather than retrying.
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:
The embedded 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
kurnl does not receive payment details. You handle payment processing entirely on your side.

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:
Successful response — 200 OK (first-line building — provisioned directly):
Successful response — 200 OK (second/third-line or unclassified building — install job):
No 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 configured webhook_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

Explicit fields (legacy, non-Marketplace flows only)

Without a session_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 an external_subscription_id (your own identifier) instead of a subscriber object. kurnl provisions the port but creates no subscriber record.
With the anonymous variant, subscribers have no kurnl self-service portal access and kurnl cannot issue invoices on your behalf.