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.
| Layer | Who bills whom | What it counts |
|---|---|---|
| Your billing | You → your customer | A game server, per month, at whatever you charge |
| Platform billing | Provibr → you | Capacity: 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 event | What should happen | Pelican-side effect |
|---|---|---|
| Order paid | Create the server from the plan | Server created from egg, allocation assigned, installer runs |
| Invoice overdue | Suspend | suspended set, so the server stops and stays stopped |
| Payment received | Resume | suspended cleared, customer starts it again |
| Cancellation | Delete | Server removed, allocation freed |
| Upgrade / downgrade | Reinstall or resize | Runs 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:
POST /v1/servers/{serverId}/actions/suspend
Authorization: Bearer pvk_...
Idempotency-Key: dunning-inv-10482{
"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:
| Action | Allowed from |
|---|---|
| start | stopped, error, unknown |
| stop / restart / kill | running, error, unknown |
| suspend | running, stopped, installing, error, unknown |
| resume | suspended |
| reinstall | running, 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
_ListAccountssync 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.