Skip to content

Blog ·

Billing automation for PufferPanel

Order to server, and unpaid invoice to stopped server, on PufferPanel. The lifecycle mapping, the async rule, and the suspend gap you have to design around.

By Provibr

Most pages about billing automation start with the happy path. This one starts with the gap, because on PufferPanel the gap determines the design.

PufferPanel has no suspend. No column, no endpoint. Server status knows only running and installing. Everything below follows from that.

Measured against PufferPanel 3.0.9.

Two billing layers, and they are not the same thing

LayerWho bills whomWhat it counts
Your billingYou → your customerA game server, per month, at whatever you charge
Platform billingProvibr → youCapacity: how many servers of each kind you may run at once

What you charge your own customers stays entirely inside your billing system. Provibr never sees a customer invoice and never talks to your customers. Your billing system owns the decision, the provisioning layer owns the execution.

The lifecycle, mapped

Billing eventWhat should happenPufferPanel-side effect
Order paidCreate the server from the planFull template definition sent inline, server created, installer runs
Invoice overdueSuspendContainer stopped + access blocked in the platform (see below)
Payment receivedResumeAccess restored; customer starts the server
CancellationDeleteServer stopped first, then removed
Upgrade / downgradeReinstallRuns the template's install again

Four of those five rows are ordinary. The second is not.

The suspend gap, and how to design around it

When a platform suspends a PufferPanel game server, to enforce a licence limit or because you told it to for non-payment, it stops the container and blocks access to that server in the platform's own panel. The server does not run, and your customer cannot start it from your dashboard.

But the block is a platform concept, not a panel one. Anyone with direct access to the PufferPanel installation can start the server again. PufferPanel is not enforcing anything, because it has nothing to enforce with.

This is worth saying plainly rather than burying, because it changes what you should sell:

Your setupDoes the gap matter?
Customers use your dashboard only; no panel accountsNo. They cannot reach PufferPanel directly.
Customers have PufferPanel loginsYes. A suspended customer can undo the suspension.
You resell to resellers with host accessYes, badly.

If you are in the second or third row and dunning enforcement matters, use a panel with a real suspend flag. Pelican has one, and a suspended server there stays suspended regardless of who logs in.

A middle route, if you want to keep PufferPanel: terminate earlier. Since a suspension is soft, treat it as a warning stage rather than an enforcement stage, and move the real enforcement to deletion, with a retention policy and a backup taken first, communicated up front. That is a product decision, not a technical one.

The rule that shapes everything: nothing is instant

Your panel lives in your own network, and a sensible platform reaches it through an agent that connects outbound, with no inbound port and no firewall hole. The consequence is unavoidable: an endpoint that changes a machine cannot answer with the result. It answers that the order was accepted:

http
POST /v1/servers/{serverId}/actions/suspend
Authorization: Bearer pvk_...
Idempotency-Key: dunning-inv-10482
json
{
  "command": {
    "id": "cmd_...",
    "kind": "game.suspend",
    "status": "pending"
  }
}

202 Accepted, not 200. You then read that command until its status is final: done, failed, timed_out or cancelled. A failed command carries the agent's own error code with its arguments. Reading is different. An inventory, an address or an invoice answers immediately, because that comes from a database and not from your host.

Design your dunning hooks around this. A hook that treats "the API returned" as "the work is done" will report a successful suspension that never happened, on precisely the day it mattered.

Repeat safely

Anything that creates something accepts an Idempotency-Key header of your own making. Repeat the same request with the same value and you get the first outcome back rather than a second server. Key it on something stable from your side: the invoice ID, the service ID, the dunning stage. Retried webhooks and fifteen-minute crons are normal, and both should be harmless. Reusing that value for a different request is refused deliberately: a key that means two things means nothing.

The verbs, and when they are allowed

A game server accepts six actions over the API: start · stop · restart · kill · suspend · resume, guarded by the server's current state:

ActionAllowed from
startstopped, error, unknown
stop / restart / killrunning, error, unknown
suspendrunning, stopped, installing, error, unknown
resumesuspended
reinstallrunning, stopped, error

You can suspend a server that is already stopped, and that is deliberate: enforcement you can evade by pressing "stop" is not enforcement. suspend from installing follows the same logic. A cancellation that lands mid-install should not wait for the install to finish.

Ordering is plan-driven, and it has to be

On some panels you can hand-build an order. On PufferPanel you effectively cannot, and the reason is structural: PufferPanel requires the full template definition inline when a server is created. There is no "make one from template X by reference", so the definition has to come from a catalogue. A game plan carries the template name and the resource limits; ordering points at the plan. If you build your own bridge you will end up building this catalogue anyway.

Two measured details that are cheap to trip over:

  • Community templates default to a host environment, which needs binaries a fresh panel host does not have. Switch to the template's docker variant, or every order fails with a missing-binary error that reads like a platform bug.
  • Deleting stops the server first. The daemon refuses to remove a running container; a delete against a running server returns a 500.

What is deliberately not there

  • No usage-based billing. PufferPanel's stats endpoint reports CPU percentage and memory only: no disk, no network, no uptime. Two of the four standard game-server graphs would be permanently empty, so usage graphs are off for this provider. Metered billing is not possible on data that does not exist.
  • No console in the dashboard. PufferPanel's console is poll-based rather than a live terminal; a spec-conforming WebSocket handshake on /console returns 200 JSON instead of 101. Your customers get the console in PufferPanel itself.
  • No capacity-aware placement. PufferPanel exposes its nodes but not their memory, disk or CPU, so a multi-node panel is picked by name order rather than free space. You are the capacity planner.
  • No plan change in one call. Swapping a plan is a reinstall, with the consequences for customer data that implies.
  • No inventory reconciliation loop. WHMCS's _ListAccounts sync terminates any service missing from the answer, and "missing" has innocent causes: a narrower API scope, a member restricted to part of the inventory, a rate limit, a short page whose cursor was not followed. Each would delete customer machines.

Doing it from WHMCS instead of code

If you already run WHMCS, Provibr ships a provisioning module as a plain zip you install yourself. One WHMCS product points at one Provibr plan; WHMCS's normal order flow does the creating, suspending, resuming and terminating. Two decisions in it are worth copying even into your own bridge:

A delivery still running counts as success. If the wait budget expires while the installer is going, the module reports success, because the order has been accepted and WHMCS has no "being installed" status (Pending there means nothing has happened yet, which is false). A command that genuinely fails is different: the service stays Pending and the admin sees why.

A server that is already gone is a successful termination. A 404 means several things at once (unknown ID, another account, not visible to this member, already deleted), and for a termination hook the right action is the same in all of them: there is nothing left to clean up.

Capacity counting, and how it meets dunning

Platform billing counts capacity, not usage, with one rule that catches everyone in month one: every server counts, running or not. Only a suspended server does not, and neither does a deleted one; stopping a machine does not free a place.

That lines up well with dunning: a customer you suspend stops counting while suspended, and counts again when they pay and you resume.

Running PufferPanel next to everything else

You do not have to pick one panel. Provibr covers its control panels, hypervisors and game panels in one dashboard. They share one API and one capacity number.

POST /v1/servers/{id}/actions/suspend does the same thing whether a Pelican server, a PufferPanel server or a DirectAdmin hosting account is underneath. The differences live in the provider, not in your integration code, and where a provider genuinely cannot do something, you get a refusal naming the missing capability rather than a silent no-op.

Start with one host and one agent

Create an account, install the agent with a single command and connect your first Proxmox host. The trial runs for 14 days and asks for no payment details.