Rate limits & security

IT / Technical

This page summarises the operational guardrails around the public API: how often you can call each endpoint, how requests are authenticated, and how your data is isolated and protected.

Rate limits

Every public endpoint is rate limited with a sliding window. Limits are keyed per IP for anonymous widget traffic, and per API key for the authenticated transactions endpoint.

EndpointLimitKeyed on
POST /api/public/book5 / 60sIP + event type
POST /api/public/capture20 / 60sIP + event type
POST /api/public/funnel-events50 / 60sIP + event type
GET /api/public/month-availability60 / 60sIP
GET /api/public/booking-summary60 / 60sIP
POST /api/public/transactions1000 / hourAPI key

When you exceed a limit you get:

HTTP 429
Retry-After: 42
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0

{ "error": "rate_limited" }

Honour Retry-After (seconds) before retrying. The transactions limit is per key, not per IP, so a script behind shared/NAT'd infrastructure still gets its full budget.

Limits fail open

Rate limiting depends on a separate store. If that store is briefly unavailable, requests are allowed through rather than rejected — a limiter outage will never drop a legitimate booking or revenue event.

Authentication

Three models are in use, depending on the endpoint (see the API reference for the per-endpoint table):

  • API key (HMAC-signed) — for transactions. You sign the exact request body with your key's secret and send Authorization: Bearer <prefix>:<sig>. Details in API keys.
  • Anonymous widget endpointscapture, book, month-availability, and funnel-events carry no credential; they're protected by rate limits, validation, and server-side checks.
  • Signed tokenbooking-summary is read with a short-lived token Preb mints for a specific booking.

PII handling

When you enable an ad-platform integration (Meta or Google), Preb hashes personal identifiers with SHA-256 before they leave the system — the raw email or phone number is never sent to the ad platform. Emails are lowercased and trimmed first; phone numbers are normalised before hashing.

Diagnostic logging is built to keep PII out: the conversion-dispatch log stores no personal data (only provider, event name, and status), and the error monitor scrubs a denylist of sensitive keys (emails, phone numbers, click IDs, tokens, secrets, cookies, API keys) from anything it captures. More detail on Data & privacy.

Row-level security

Preb is multi-tenant and isolates every workspace's data at three layers:

  1. Database row-level security — Postgres policies restrict each query to the rows owned by the requesting workspace; anonymous access sees nothing, and cross-tenant reads and writes are rejected at the database.
  2. Application scoping — server code resolves the current workspace and adds a workspace filter to every query, and checks the caller's role before any write.
  3. Authentication — every dashboard action verifies the signed-in user and their membership first.

Roles are hierarchical: OwnerAdminMember. Requests for a workspace you don't belong to return 404 (so workspace existence can't be probed), and under-privileged members are refused writes. API keys are workspace-scoped — a key can only ever touch its own workspace's data.

Reporting a vulnerability

Found a security issue?

Please report it privately rather than filing a public issue, and give us reasonable time to fix it before disclosing. Reach the team through Contact support and flag the message as a security report so it's routed appropriately.

Restricting where the widget can be embedded

By default a booking page can be embedded on any website. Under Workspace settings → Einbetten an admin can list the websites allowed to embed it (hostnames; subdomains included). Once the list has an entry, Preb sends a frame-ancestors policy naming only those sites, and any other page that tries to frame the widget shows an empty frame. The hosted booking page is unaffected. The setting suggests the sites the widget has already been seen on, so it can be locked down in two clicks after the first successful install.