When to use this
Use CKO-03 direct delivery when:
- The subscriber’s building is already wired — a switch port exists and is assigned to their unit
- 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 subscriber’s unit does not yet have a port installed, use CKO-03 home-drop delivery instead.
How it works
Step-by-step
In Dashboard → Settings → Integration, set your external_checkout_url. The kurnl Marketplace appends three query parameters when redirecting subscribers to your page:
| Parameter | Description |
|---|
location_hash | Opaque 10-character token identifying the subscriber’s port location. Forward verbatim — do not parse or modify. |
plan_version_id | UUID of the plan version the subscriber selected on the marketplace. |
service_provider_document_id | Your provider UUID. Validates the request came from kurnl. |
Example redirect:
https://checkout.yourcompany.com/internet?location_hash=ab12cd34ef&plan_version_id=f7e8...&service_provider_document_id=a1b2...
Read and store all three values on page load. You need them verbatim in the callback.
2. 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.
3. Call kurnl after successful payment
After payment succeeds, POST to /partner/external-checkout/complete:
curl -X POST https://api.kurnl.ca/api/v1/partner/external-checkout/complete \
-H "Content-Type: application/json" \
-H "X-Webhook-Secret: YOUR_WEBHOOK_SECRET" \
-d '{
"service_provider_document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"plan_version_id": "f7e8d9c0-b1a2-3456-cdef-012345678901",
"delivery_mode": "direct",
"location_hash": "ab12cd34ef",
"subscriber": {
"email": "alice@example.com",
"username": "alice",
"password": "secure-password-123",
"customer_type": "RESIDENTIAL",
"mac_address": "AA:BB:CC:DD:EE:FF",
"invoice_contact_detail": {
"firstname": "Alice",
"lastname": "Smith",
"email": "alice@example.com",
"phonenumber": "+16045551234"
},
"contract_contact_detail": {
"firstname": "Alice",
"lastname": "Smith",
"email": "alice@example.com"
},
"invoice_address": {
"street": "Maple St",
"housenumber": "42",
"postalcode": "A1B 2C3",
"city": "Vancouver",
"province": "BC",
"country": "Canada"
}
}
}'
Successful response — 200 OK:
{
"job_id": "3d6e8f12-...",
"subscription_id": "8c4a1b9e-...",
"subscriber_id": "2f5d7a0c-...",
"message": "Provisioning started"
}
Store the subscription_id and subscriber_id in your system — you’ll need them for any future subscriber management calls.
4. Handle webhook events
kurnl fires events to your configured webhook_url at each stage:
| Event | When | Key fields |
|---|
subscription.activated | Immediately after your callback | subscription_id, subscriber_id, plan_version_id |
provisioning.completed | ~30 seconds later, port is live | job_id, subscription_id |
provisioning.failed | If SSH provisioning fails | job_id, subscription_id, error |
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
Required fields
| Field | Type | Notes |
|---|
service_provider_document_id | UUID | Your provider UUID — used to look up your webhook secret |
plan_version_id | UUID | From the marketplace redirect URL |
location_hash | string (10 hex chars) | From the marketplace redirect URL. Forward verbatim. |
subscriber.email | string | Kurnl account identifier. Existing accounts are reused. |
subscriber.username | string (min 3) | Display name |
subscriber.invoice_contact_detail | object | See contact detail fields below |
subscriber.contract_contact_detail | object | Can be identical to invoice contact |
subscriber.invoice_address | object | See address fields below |
Optional fields
| Field | Type | Notes |
|---|
delivery_mode | "direct" | "home_drop" | Defaults to "direct" |
subscriber.password | string (min 8) | Only set for new accounts. Existing accounts keep their password. |
subscriber.customer_type | "RESIDENTIAL" | "BUSINESS" | Defaults to "RESIDENTIAL" |
subscriber.mac_address | string | Enables port security (static MAC binding) on the switch |
| Field | Type |
|---|
firstname | string |
lastname | string |
email | string |
phonenumber | string (optional) |
company | string (optional) |
Address fields
| Field | Type |
|---|
street | string |
housenumber | string |
postalcode | string |
city | string |
province | string |
country | string (default: "Canada") |
unitnumber | string (optional) |
floor | string (optional) |
Error handling
| Status | Error | What to do |
|---|
| 401 | X-Webhook-Secret header required | Add the header |
| 401 | Invalid webhook secret | Check your secret matches what’s in the dashboard |
| 403 | Plan version does not belong to your service provider | Wrong plan_version_id for your account |
| 404 | Location not found for location_hash | The hash didn’t match any location record — subscriber may have used an expired link |
| 422 | Invalid location_hash format | Must be exactly 10 hex characters |
| 400 | Plan version not active | The plan’s effective date window has passed |
| 429 | Rate limit exceeded | 120 requests/minute per IP |
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 email + plan_version_id combination returns the existing job_id rather than creating a duplicate.
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.