Skip to content

Blog ·

Billing automation for Pelican Panel

From paid order to running Pelican server, and from unpaid invoice to a suspended one. The lifecycle mapping, the async rule, and where the honest limits are.

By Provibr

Pelican has a suspended flag on every server. Nothing in Pelican ever sets it. That single sentence is the whole reason this page exists: the panel is capable of stopping a customer's server, but it has no idea whether the customer paid.

This page covers what a working automation looks like: the order path, the dunning path, and the parts that are genuinely harder than they look.

Two billing layers, and they are not the same thing

Before anything else, separate them. People conflate these constantly and then argue about the wrong problem.

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, and how, stays entirely inside your billing system. Provibr never sees a customer invoice, never sets a price for you, and never talks to your customers; it bills you for reserved capacity per resource type, at a unit price that falls in tiers as the count rises.

That separation is what makes the automation tractable. Your billing system owns the decision; the provisioning layer owns the execution.

The lifecycle, mapped

Here is the mapping every game hosting automation needs, whatever tools you use:

Billing eventWhat should happenPelican-side effect
Order paidCreate the server from the planServer created from egg, allocation assigned, installer runs
Invoice overdueSuspendsuspended set, so the server stops and stays stopped
Payment receivedResumesuspended cleared, customer starts it again
CancellationDeleteServer removed, allocation freed
Upgrade / downgradeReinstall or resizeRuns the egg's install script again

Pelican is comfortable here, and that is worth saying plainly, because not every game panel is. Pelican has a real suspend primitive. A suspended server is suspended in the panel: someone with direct access to Pelican cannot simply start it again. When the enforcement matters, and for dunning it does, that is the difference between a policy and a wish.

(For contrast: PufferPanel has no suspend of its own, so a suspension there is a platform concept the panel does not enforce.)

The rule that shapes everything: nothing is instant

This is the part people get wrong when they wire up an API themselves.

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, and hands you something to follow:

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

You get 202 Accepted, not 200. Then you read that command until its status is final: done, failed, timed_out or cancelled. A command that failed carries the error code of the agent itself, with its arguments.

Reading never works this way. An inventory, an address or an invoice answers immediately, because that answer comes from a database and not from your host.

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

Repeating a request 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, instead of a second server, a second command, or a second charge.

For dunning this is not a nicety. A fifteen-minute cron, a retried webhook, an admin who clicks twice. All three are normal and all three should be harmless. Key the request on something stable from your side: the invoice ID, the service ID, the dunning stage. Reusing that value for a different request is refused on purpose: 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

They are guarded by the server's current state, which is stricter than it may look:

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

The suspend row is the interesting one, and it is deliberate: you can suspend a server that is already stopped. If suspension only worked on running servers, a customer could stop their own server, stop paying, and be immune: the panel would refuse the suspension because there was nothing running. Enforcement you can evade by pressing "stop" is not enforcement.

suspend from installing matters too: a cancellation that lands mid-install should not wait for the install to finish.

Doing it from a billing panel instead of code

If you already run WHMCS, you do not need to write any of the above. Provibr ships a provisioning module for WHMCS as a plain zip you install yourself: one WHMCS product points at one Provibr plan, and WHMCS's normal order flow does the creating, suspending, resuming and terminating.

Two design decisions in that module are worth stealing even if you write your own bridge:

A delivery that is still running counts as success. If the wait budget runs out while the installer is still going, the module reports success. The order has been accepted: the server row exists, the address is assigned, the command is with the agent. WHMCS has no "being installed" status (Pending there means nothing has happened yet, which is demonstrably false). A command that genuinely fails is different: the service stays Pending, the admin sees why, and a repeated click is harmless thanks to the idempotency key.

A server that is already gone is a successful termination. A 404 on delete means four things at once (unknown ID, another account, not assigned to this member, or already deleted), and for a termination hook the correct action is identical in all four: there is nothing left to clean up. Treating it as an error leaves a service stuck in "terminating" forever.

What is deliberately not automated

Honest boundaries, because these are the questions that arrive after you have already sold something:

  • No usage-based billing. Per-server metrics are collected and shown in the panel, but not yet exposed over the API. Without an endpoint there is no metered billing. If your product is "pay for the RAM you used", this does not do it today.
  • No plan change in one call. Swapping a plan is not a single API call at the Pelican level either. It is a reinstall or a resource change, with different consequences for the customer's data.
  • No password reset over the API. Credentials travel transiently and are never stored, so there is nothing to re-send.
  • No single sign-on from your billing panel into the Provibr panel.
  • No inventory reconciliation loop, a refusal rather than a gap. 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. The client area reads live status on every page view instead, which is not synchronisation and does not pretend to be.

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 neatly with dunning. A customer you suspend for non-payment stops counting against your capacity while suspended, and counts again the moment they pay and you resume, so you are not paying platform capacity for a customer who is not paying you.

Going over your limit is not blocked at the moment you do it. It is detected, you are told, and you get a full working day. If that grace expires, the newest servers above the limit are suspended, newest first, so what you built earliest survives.

Running Pelican next to everything else

A game host that grows rarely stays on one panel. Provibr covers its control panels, hypervisors and game panels in one dashboard. They share one API and one capacity number across all of them.

POST /v1/servers/{id}/actions/suspend does the same thing whether a Pelican server, a Pterodactyl server or a DirectAdmin hosting account is underneath. The differences live in the provider, not in your integration code.

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.