HiLobby API v1
REST API for the PRO+ plan: profile, links, stats, donations, messages, live status and webhooks.
On this page
Quick start
Three steps are enough:
- In the dashboard open the "API and webhooks" tab and create a key with the permissions you need.
- Copy the key (it is shown only once) and store it on your server.
- Send a request with the Authorization header. The /api/v1 response shows the key permissions and limits.
curl -H "Authorization: Bearer lp_live_..." https://145-239-29-97.sslip.io/api/v1
Base URL: https://145-239-29-97.sslip.io/api/v1 · OpenAPI 3.1 (JSON)
Authentication
Every request (except the OpenAPI specification) needs a key in the Authorization (Bearer) or X-Api-Key header.
Authorization: Bearer lp_live_0123456789abcdef0123456789abcdef01234567
X-Api-Key: lp_live_0123456789abcdef0123456789abcdef01234567
- Keys start with
lp_live_. We store only a hash, so a lost key cannot be recovered: roll the secret or create a new key. - Do not use a key in website or mobile app code. The API is meant to be called from a server.
- You can restrict a key to IP addresses (single addresses or CIDR ranges) and set an expiry date.
- When PRO+ expires, keys stop working (
plan_requirederror) and work again once the plan is renewed.
Permissions
Each category has a read level (:read) and, where it makes sense, a write level (:write). Write includes read.
| Category | Permissions | What it covers |
|---|---|---|
| Profile | profile:read profile:write | Name, bio, address, plan; write: name, bio, message button |
| Links | links:read links:write | Link list; write: add, edit, reorder, delete |
| Statistics | stats:read | Views, clicks, sources, devices, live traffic |
| Donations | donations:read donations:write | History, summaries, goal; write: moderation, goal changes |
| Messages | messages:read messages:write | Message inbox; write: mark as read, delete |
| LIVE status | live:read live:write | Stream status; write: turning LIVE on and off |
| Webhooks | webhooks:read webhooks:write | List and delivery history; write: add, edit, test |
Give keys only the permissions they need. For example, a stream overlay usually needs donations:read and live:read.
Rate limits
- Up to 1000 requests per hour per key (PRO+ plan setting).
- An additional short burst limit protects against sudden request spikes.
- Up to 10 keys and 5 webhooks per account.
- Every response has X-RateLimit-* headers. When you exceed the limit you get a 429 status and a Retry-After header.
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1792040400
Retry-After: 42
Response format
A successful response has a data field, and lists also have next. An error has an error field with a code and a message.
{"ok": true, "data": { ... }, "next": 1234}
{"ok": false, "error": {"code": "insufficient_scope", "message": "...", "required_scope": "donations:read"}}
- Amounts are given in zloty (
amount) and in grosze (amount_minor). - Dates are in UTC in ISO 8601 format, e.g.
2026-10-15T18:42:07Z. - Every response has an
X-Request-Idheader. Include it when you report a problem.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | No key in the request. |
invalid_key | 401 | The key does not exist or has been revoked. |
key_expired | 401 | The key expiry date has passed. |
account_inactive | 403 | The account is blocked or inactive. |
plan_required | 403 | The feature requires PRO+ (or another plan for that feature). |
ip_not_allowed | 403 | The request came from an address outside the key IP allowlist. |
insufficient_scope | 403 | The key lacks the required permission (see required_scope). |
not_found | 404 | The resource or endpoint does not exist. |
conflict | 409 | The operation does not fit the current state (e.g. the donation is not waiting for review). |
invalid_json | 400 | The request body is not valid JSON. |
invalid_request | 400 | Invalid request headers. |
validation_error | 422 | Invalid data (see message). |
rate_limited | 429 | Rate limit exceeded. |
server_error | 500 | An error on our side. Try again, and if it persists, contact us with the X-Request-Id. |
maintenance | 503 | Maintenance in progress. Try again later (see the Retry-After header). |
Pagination
Lists return the newest items first. The limit parameter (1-100, default 25) sets the page size; pass next from the response as after to fetch the next page. When next is null, you have reached the end.
GET /api/v1/donations?limit=50
GET /api/v1/donations?limit=50&after=1234
Idempotency
Requests that change data (POST, PATCH, DELETE) can include an Idempotency-Key header (up to 100 characters). Repeating a request with the same key within 24 hours returns the stored response (Idempotent-Replayed: true header) instead of performing the operation again. Useful when retrying after a network error.
curl -X POST -H "Authorization: Bearer lp_live_..." -H "Idempotency-Key: 6f1c2a" \
-H "Content-Type: application/json" -d '{"title":"Nowy film","url":"https://youtu.be/xyz"}' \
https://145-239-29-97.sslip.io/api/v1/links
Endpoints
Paths are relative to /api/v1. Send request bodies as JSON (Content-Type: application/json). An asterisk marks a required field.
panel.api_cat_
| Method | Path | Permission | Description |
|---|---|---|---|
GET | / | any key | Key, plan and limit information |
GET | /openapi.json | no key | OpenAPI 3.1 specification (no key needed) |
GET | /events | any key | List of webhook events |
Profile
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /me | profile:read | Profile details |
PATCH | /me | profile:write | Update name, bio and the message button Parameters: name string, bio string, messages_enabled boolean |
Links
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /links | links:read | List of links and sections in order |
POST | /links | links:write | Add a link (optionally at a given position) Parameters: title* string, url* string, subtitle string, tag string, enabled boolean, featured boolean, position integer |
GET | /links/{id} | links:read | A single link |
PATCH | /links/{id} | links:write | Update a link: title, URL, enabled, position Parameters: title string, url string, subtitle string, tag string, enabled boolean, featured boolean, position integer |
DELETE | /links/{id} | links:write | Delete a link |
Statistics
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /stats | stats:read | Statistics: daily views, unique visitors, clicks, sources, links, devices Parameters: days integer, from date, to date |
GET | /stats/realtime | stats:read | Live traffic (last 30 minutes) |
GET | /stats/mediakit | stats:read | Media kit: reach, growth, engagement, countries, devices, sources and community (aggregated data) Parameters: days integer |
Donations
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /donations | donations:read | List of donations (status and date filter) Parameters: limit integer, after integer, status string, since datetime |
GET | /donations/summary | donations:read | Summary: totals for 24 h, 7 and 30 days, top supporters, goal |
GET | /donations/{id} | donations:read | A single donation |
POST | /donations/{id}/approve | donations:write | Approve a donation from the review queue |
POST | /donations/{id}/reject | donations:write | Reject a donation from the review queue (optional reason) |
GET | /donations/goal | donations:read | Fundraising goal and progress |
PATCH | /donations/goal | donations:write | Update the fundraising goal Parameters: enabled boolean, name string, desc string |
Messages
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /messages | messages:read | Messages from visitors Parameters: limit integer, after integer, unread boolean |
PATCH | /messages/{id} | messages:write | Mark a message as read or unread Parameters: read* boolean |
DELETE | /messages/{id} | messages:write | Delete a message |
LIVE status
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /live | live:read | LIVE status and channels |
POST | /live | live:write | Turn LIVE status on or off (manual mode) Parameters: live* boolean, platform string, title string |
Webhooks
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /webhooks | webhooks:read | List webhooks |
POST | /webhooks | webhooks:write | Create a webhook (secret returned once) Parameters: url* string, events* array, description string |
PATCH | /webhooks/{id} | webhooks:write | Update a webhook: URL, events, enabled Parameters: url string, events array |
DELETE | /webhooks/{id} | webhooks:write | Delete a webhook |
POST | /webhooks/{id}/test | webhooks:write | Send a test ping event |
GET | /webhooks/{id}/deliveries | webhooks:read | Webhook delivery history (30 days) Parameters: limit integer, after integer |
POST | /webhooks/{id}/deliveries/{delivery}/retry | webhooks:write | Redeliver a delivery |
Changes saved through the API appear in the dashboard after a page refresh. If the dashboard is open, refresh it before editing so that you do not overwrite API changes.
Webhooks
A webhook sends an event to your https address as soon as something happens. Add webhooks in the dashboard or through the API. Through the API you can subscribe to an event only if the key can read that category.
| Event | Required permission | Description |
|---|---|---|
donation.received | donations:read | New paid donation (status: approved, pending_review or rejected) |
donation.approved | donations:read | Donation approved from the review queue |
donation.rejected | donations:read | Donation rejected from the review queue |
message.received | messages:read | New message from a visitor |
live.started | live:read | Stream started (manually, automatically or via the API) |
live.ended | live:read | Stream ended |
link.created | links:read | Link added (in the dashboard or via the API) |
link.updated | links:read | Link updated |
link.deleted | links:read | Link deleted |
ping | – | Test event from the dashboard or the API |
Sample request:
POST /twoj-adres HTTP/1.1
Content-Type: application/json
User-Agent: HiLobby-Webhooks/1.0
X-HiLobby-Event: donation.received
X-HiLobby-Delivery: evt_3f9a1c0b7d2e4a6f8b1c
X-HiLobby-Timestamp: 1792003327
X-HiLobby-Signature: t=1792003327,v1=5f2b...e91a
{
"id": "evt_3f9a1c0b7d2e4a6f8b1c",
"type": "donation.received",
"created_at": "2026-10-15T18:42:07Z",
"api_version": "1.0",
"data": {
"id": 1234,
"amount": 25,
"amount_minor": 2500,
"currency": "PLN",
"nick": "Kuba",
"message": "Pozdrawiam!",
"email": null,
"status": "approved",
"reject_reason": null,
"provider": "BLIK / przelew",
"thanked": false,
"created_at": "2026-10-15T18:41:50Z",
"paid_at": "2026-10-15T18:42:07Z"
}
}
- Respond with a 2xx status within 8 seconds. Do heavy work later (in a queue) to avoid timeouts.
- Failed deliveries are retried after: 1 min, 5 min, 30 min, 2 h, 6 h, 12 h. After that the delivery is marked "failed" and you can redeliver it manually in the dashboard or through the API.
- After 15 failed events in a row the webhook is disabled and we send you an e-mail.
- An event may arrive more than once or out of order. The
idfield is unique, so use it to skip duplicates. - We only send to public https addresses, without following redirects.
- Deprecated headers: headers carrying the old brand name (from before the switch to
X-HiLobby-*) have been removed - every event is now signed exclusively withX-HiLobby-*headers.
Signature verification
The X-HiLobby-Signature header looks like t=<unix time>,v1=<hex>. Compute HMAC SHA-256 of t + "." + body with the webhook secret and compare it with v1 in constant time. Reject events older than 5 minutes.
Node.js
const crypto = require('crypto');
// rawBody: tresc zadania jako tekst (przed JSON.parse)
function verify(rawBody, header, secret) {
const p = Object.fromEntries(header.split(',').map(x => x.split('=')));
const mac = crypto.createHmac('sha256', secret).update(p.t + '.' + rawBody).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(p.t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(p.v1));
}
PHP
<?php
$raw = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_HILOBBY_SIGNATURE'] ?? ''), $p);
$mac = hash_hmac('sha256', ($p['t'] ?? '') . '.' . $raw, getenv('HILOBBY_WEBHOOK_SECRET'));
if (!hash_equals($mac, $p['v1'] ?? '') || abs(time() - (int) ($p['t'] ?? 0)) > 300) {
http_response_code(400);
exit;
}
$event = json_decode($raw, true);
// ... obsluga $event['type'] i $event['data']
http_response_code(200);
Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
p = dict(x.split('=', 1) for x in header.split(','))
mac = hmac.new(secret.encode(), (p['t'] + '.').encode() + raw_body, hashlib.sha256).hexdigest()
return abs(time.time() - int(p['t'])) < 300 and hmac.compare_digest(mac, p['v1'])
Examples
Latest approved donations
curl -H "Authorization: Bearer $LP_KEY" "https://145-239-29-97.sslip.io/api/v1/donations?status=approved&limit=10"
Donation summary (e.g. for an overlay)
curl -H "Authorization: Bearer $LP_KEY" https://145-239-29-97.sslip.io/api/v1/donations/summary
Turning LIVE status on from your own script
curl -X POST -H "Authorization: Bearer $LP_KEY" -H "Content-Type: application/json" \
-d '{"live": true, "platform": "twitch", "title": "Granie z widzami"}' https://145-239-29-97.sslip.io/api/v1/live
Hiding a link
curl -X PATCH -H "Authorization: Bearer $LP_KEY" -H "Content-Type: application/json" \
-d '{"enabled": false}' https://145-239-29-97.sslip.io/api/v1/links/a1b2c3d4e5
Adding a webhook
curl -X POST -H "Authorization: Bearer $LP_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/hilobby", "events": ["donation.received", "order.paid"]}' \
https://145-239-29-97.sslip.io/api/v1/webhooks
Versions and changes
Current version: 1.0. New fields and endpoints are added without changing the version, so ignore unknown fields. Breaking changes will go to a new path (e.g. /api/v2), and we will announce the retirement of an old version in advance.
API use is governed by the Terms. Personal data retrieved via the API (e.g. supporter emails) is processed by the user as controller.