Sessions are how the kurnl Marketplace hands a subscriber over to your hosted checkout page. The Marketplace creates a session, redirects the subscriber to your external_checkout_url with ?session_id=<id> appended, and your page reads the session to get the plan, location, and delivery mode. See the flow guides: CKO-03 Direct, CKO-03 Home-Drop, CKO-03 Anonymous.

Endpoints

A session you have read does not expire. The first time your checkout page reads a session (GET .../sessions/{session_id}), kurnl marks it redirected — the shopper has arrived on your page — and from then on it is never reaped. It ends as completed or cancelled. So a slow payment, a bank transfer, or a customer who finishes the next morning can still be provisioned. A session that is never read expires 2 hours after it is created. That window only has to cover the hop from the kurnl Marketplace to your checkout page. Any endpoint that touches such a session returns 410 Gone and marks it expired on the same call; that is final, there is no way to revive it. Session lifecycle: pending → redirected → completed | cancelled, or pending → expired if your page never read it. expired and cancelled surface as 410.
Read the session once, early, when your checkout page renders — that read is what protects the order. If your integration caches the session body and never calls kurnl again before payment, the session stays pending and can still expire under you.

POST /partner/external-checkout/sessions

Creates a pending session and returns your hosted checkout URL with session_id appended.
This is the kurnl Marketplace’s endpoint, not part of the partner contract. It is documented so you can read what arrives in the session your page receives — do not call it. If you originate checkouts yourself, because customers buy on your own site, that is CKO-04 provider-initiated: resolve the address with the serviceability endpoints and provision with POST /provisioning/provider-initiated. No session is involved.
Auth: none required. If a signed-in kurnl user’s Bearer token is supplied, the session is linked to that account (so it appears in their hub).

Request body

discount_code is a provider code or a kurnl operator discount code the shopper entered on kurnl before the hand-over (see validate-discount-code). A code that does not resolve answers 400 Invalid or expired discount code and no session is created.

Response — 201 Created

In sandbox, external_checkout_url points to the kurnl fake-Stripe page instead of your configured checkout URL. If your provider has no external_checkout_url configured, the endpoint returns 503. With a discount_code, the hosted URL gains &discount_code=. Kurnl stores the code on the session and lowers its own wholesale price when the order completes; it cannot show your price. You are trusted to pass the discount on to the customer. Creating or patching a session does not check the building’s bulk offer. The 422 bulk_plan refusal comes at external-checkout-complete and in the anonymous callback.

Error codes


GET /partner/external-checkout/sessions/

Returns the current state of a session, with the referenced plan, unit/building, and provider embedded for display on your checkout page. Auth: X-Webhook-Secret header.

Response — 200 OK

discount_code is the code the session carries, and operator_discount the kurnl operator discount it resolves to (both null without one). Show the shopper promo_price_monthly for duration_months months (null = for the life of the line), then list_price_monthly. If the discount cannot be read, the call answers 503: retry. operator_offers lists every kurnl operator discount the shopper may use on this session’s building, with the monthly price it gives on this plan (promo_price_monthly for duration_months months, null = life of the line, then list_price_monthly). Take the code on your checkout page, match it against this list (case-insensitive), show the reduced summary, and send it as discount_code on the completion call. operator_offers is always present: an empty list means no kurnl operator code is valid on this building, so you can switch on the field’s presence. If the offers cannot be read, the call answers 503: retry. units.postal_unit is the unit’s own postal label as stored (e.g. "200" on connection 200-1), or null when the unit has none. It does not say whether the suite is shared: a connection whose unit number is the suite itself (a bare "200" next to 200-1 and 200-2) has null, and a unit alone in its suite may carry one. When the order is completed on a suite with several connections, kurnl may place it on a different connection and hold it for the operator (the completion response then has unit_confirmation_pending: true) — see POST /external-checkout/complete. expired and cancelled sessions are never returned as a body — they yield 410 Gone. The embedded units.buildings object carries the full postal address of the building the order is for — street, city, postal code, province and country. There is no buildings endpoint on the partner API, so this embed is the only place that address reaches you: read it from the session rather than expecting to look a building up by its id. The fields are nullable, because kurnl holds addresses for buildings at varying levels of completeness — render defensively. woocommerce_product_id is the intended product-mapping key for store integrations: set it on the plan version to join a kurnl plan to the product in your own catalog.
The TTL check runs before authentication by design — an expired session returns 410 even with a missing or wrong X-Webhook-Secret. It only ever applies to a session still in pending.

Error codes


PATCH /partner/external-checkout/sessions/

Upsell endpoint: swap the plan while the subscriber is still on your checkout page, or attach your own reference id. Only pending/redirected sessions are patchable. Auth: X-Webhook-Secret header.

Request body

Only these two fields are patchable — anything else (including unit_id, delivery_mode, service_provider_document_id) is rejected with 422.

Response — 200 OK

Returns the updated session object (same shape as GET).

Error codes