HF PanelDocsv1.0.0

Webhooks

The Stripe and PayPal webhook addresses HF Panel listens on, how to set them up, and the signed alerts HF Panel sends out.

Webhooks are how payment providers tell HF Panel that something happened: a card payment went through, a subscription was cancelled. HF Panel then creates, renews or suspends the hosting account. HF Panel can also send its own alerts to your service as webhooks.

Payment webhooks (received by HF Panel)

Provider Address Old address, still accepted
Stripe https://api.yourdomain.com:2087/v1/webhooks/stripe https://panel.yourdomain.com:8443/billing/webhook
PayPal https://api.yourdomain.com:2087/v1/webhooks/paypal https://panel.yourdomain.com:8443/billing/paypal-webhook

The Set up (once) card on the Billing screen shows the exact address for your server, with the old one under it. Webhooks set up earlier on the old address keep working.

Where: Server Admin › Billing › Billing

Stripe

  1. In Stripe, open Developers → Webhooks → Add endpoint, and enter the address from the Set up (once) card.
  2. Choose these events: checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, invoice.paid, invoice.payment_failed, customer.subscription.updated, customer.subscription.deleted.
  3. Copy the endpoint's Signing secret (it starts with whsec_).
  4. In HF Panel, on the Stripe — card payments card, paste it into Webhook signing secret together with your Secret key, then click Save.

The card shows Connected when both are saved, or Webhook secret missing when only the secret key is.

Important: without the webhook signing secret, paid card orders do not become hosting accounts. HF Panel refuses to open the order page with Stripe until the secret is saved.

How HF Panel checks each Stripe request:

  • Only POST is accepted.
  • The Stripe-Signature header must be a valid HMAC-SHA256 signature made with your signing secret, no more than 5 minutes old.
  • A request that fails these checks answers 400 {"error":"invalid signature"}. A body without an event type answers 400 {"error":"invalid event"}.
  • A handled event answers 200 {"received": true, "result": …}. When handling fails, HF Panel logs it and answers 500 so that Stripe tries again.

PayPal

You do not add the PayPal webhook by hand. HF Panel creates it for you:

  1. At developer.paypal.com, open Apps & Credentials → Create App (Merchant).
  2. In HF Panel, on the PayPal card, paste the app's Client ID and Secret, choose the Mode (Sandbox — test payments or Live — real payments) and click Save.
  3. Click Connect PayPal. HF Panel checks the keys and creates, or updates, a webhook pointing at the PayPal address above.

HF Panel subscribes to these PayPal events: BILLING.SUBSCRIPTION.ACTIVATED, BILLING.SUBSCRIPTION.RE-ACTIVATED, BILLING.SUBSCRIPTION.UPDATED, BILLING.SUBSCRIPTION.CANCELLED, BILLING.SUBSCRIPTION.SUSPENDED, BILLING.SUBSCRIPTION.EXPIRED, BILLING.SUBSCRIPTION.PAYMENT.FAILED, PAYMENT.SALE.COMPLETED and PAYMENT.CAPTURE.COMPLETED.

When you switch from Sandbox to Live, save the live app's keys and click Connect PayPal again.

How HF Panel checks each PayPal request:

  • Only POST is accepted, and only when PayPal is set up; otherwise it answers 400 {"error":"not set up"}.
  • HF Panel asks PayPal's own verification service whether the message is genuine, using the Paypal-Transmission-Id and the other Paypal-… headers. A message that fails answers 400 {"error":"invalid signature"}.
  • A handled event answers 200 {"received": true, "result": …}; a failure answers 500 so that PayPal tries again.

Note: the webhook addresses are for the payment providers only. If you use your own billing system instead, see Billing integration.

Notification webhooks (sent by HF Panel)

HF Panel can send alerts, such as a website going down or a backup failing, to your own address. Add a webhook channel on the Notifications screen and enter its Webhook address. The screen shows the channel's signing secret.

HF Panel sends one POST per alert, with Content-Type: application/json and these headers:

http
X-HF-Event: uptime.down
X-HF-Delivery: 812
X-HF-Timestamp: 1793023200
X-HF-Signature: sha256=5c1f…
json
{
    "id": 311,
    "delivery": 812,
    "event": "uptime.down",
    "name": "Website down",
    "title": "Website down: https://example.com/",
    "text": "https://example.com/ is not answering (HTTP 500).",
    "link": "https://panel.yourdomain.com:8443/?r=uptime",
    "account": "sarahbakes",
    "account_id": 3,
    "urgent": true,
    "test": false,
    "time": "2026-10-24T09:20:00Z",
    "data": {}
}

Check the signature

X-HF-Signature is sha256= followed by the hexadecimal HMAC-SHA256 of <X-HF-Timestamp>.<body>, made with the channel's signing secret.

python
import hmac, hashlib, time

def genuine(secret: bytes, ts: str, body: bytes, header: str) -> bool:
    if abs(time.time() - int(ts)) > 300:      # refuse old messages
        return False
    want = "sha256=" + hmac.new(secret, ts.encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, header)

Answer and retries

  • Answer with any 2xx status.
  • A 5xx, 408 or 429 answer, or no answer, is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours.
  • Any other 4xx counts as failed at once. Redirects are not followed.
  • The Send test button sends "event": "test" with "test": true.

Event names include uptime.down, uptime.up, ssl.14, ssl.7, ssl.1, quota.80, quota.90, quota.100, backup.failed and login.new_ip.