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