For integrations where you own all billing (e.g. a store that runs WooCommerce Subscriptions,
or a billing platform like Stripe Billing): when your
billing system puts a subscription on hold, tell kurnl to suspend the port; when it becomes
active again, tell kurnl to restore it. When the customer leaves, tell kurnl to cancel it.
No kurnl invoices or fees are created either way — kurnl only performs the network action.
Endpoint
Authentication
X-Webhook-Secret header — same secret as the other external-checkout endpoints.
Request body
Identify the subscription in one of three ways:
- A —
session_id: the original checkout session.
- B —
subscription_id + service_provider_document_id: kurnl’s own identifier, as
returned by the completion call.
- C —
external_subscription_id + service_provider_document_id: your own
per-subscription identifier, the same value you sent as external_subscription_id when
you completed the checkout.
Option C is the one to build against. It means you never have to store a kurnl
identifier: the key you already own addresses the subscription for the whole of its life.
An external_subscription_id kurnl has no subscription for returns 404.
Response — 200 OK
suspend resets the switch port to the captive portal, marks the subscription
SUSPENDED, and sets is_active: false. That applies to an ACTIVE subscription.
- Before the install,
suspend holds the technician visit instead and answers 200
with subscription_status: "PENDING_INSTALL". A home-drop order is PENDING_INSTALL
until the technician has been, so this is the ordinary case, not an error: kurnl cancels
any booked visit, releases the slot and stops the booking link. The subscription is not
marked SUSPENDED — it was never installed. restore lifts the hold and sends a fresh
booking link, again answering 200 with PENDING_INSTALL.
- A
CANCELLED or TERMINATED subscription cannot be suspended — see the 409 below.
- Read
subscription_status from the response rather than assuming the action implies
a status; before the install both directions report PENDING_INSTALL.
restore only reverses a suspend: it queues port re-provisioning, marks the
subscription ACTIVE, and clears kurnl’s dunning/deprovision markers
(disconnection notice, exception flag, deprovisioned-at). A subscription in any
status other than SUSPENDED cannot be restored — see the 409 below.
restore is all-or-nothing. If kurnl cannot re-open the port — missing switch port,
VLAN or plan bandwidth — it returns 409 naming what is missing and changes nothing:
the subscription stays SUSPENDED. It will never report ACTIVE over a port the suspend
left on the captive portal. Retry once the underlying data is fixed (ask kurnl operations).
- Repeating a call for a subscription already in the target state (suspend an
already-
SUSPENDED, restore an already-ACTIVE) is an idempotent 200 with
port_changed: false and an “Already …” message.
Cancelling a line
cancel ends the subscription. It is the partner twin of the customer’s own cancel, and it
sends you subscription.cancelled (and later subscription.deprovisioned) as a
webhook.
end_date (optional, YYYY-MM-DD) is the first day without service, in your
provider’s local day. The line runs through the day before it. "end_date": "2026-11-01"
means the last day of service is 31 October. Leave it out and the line ends today.
- Today on a live line (
end_date is today, or left out) means the service runs through
today and stops at the end of the day.
- A line not yet installed (
PENDING or PENDING_INSTALL) ends at once. Its port stays
reserved.
- A date in the past is refused with
422 (“The earliest cancellation date is today, …”).
reason (optional, up to 200 characters, default partner_cancelled) is kept as the
cancellation reason and comes back as cancellation_reason in the webhook.
- The response carries
ended_at, the cut-off instant: midnight at the start of the
end_date day in your provider’s local time zone, given in UTC (an ISO 8601 timestamp
with offset +00:00; for a provider in Vancouver, end_date 2026-11-01 is
2026-11-01T07:00:00+00:00). The service stops at that instant. subscription_status is "CANCELLED". port_changed is false: the switch is reset when the line is deprovisioned.
- Cancelling a line that is already cancelled answers
409 and sends no second webhook. A request
that arrives while another change to the same line is still running (a second cancel, or a
cancellation from the dashboard) answers 409 action_in_progress and changes nothing.
Request:
Response:
end_date and reason apply to cancel only. kurnl ignores them on suspend and restore.
Reading port_changed and port_error
Error codes