litehook Docs Sign in

Security

How Litehook checks requests, stores events, reaches your devices, and handles leaked secrets.

In short
Secrets256 random bits per source, shown once: a signing secret encrypted at rest, or a URL token stored only as a SHA-256 hash.
CheckingConstant-time comparison. On URL token sources, a wrong secret gets the same 401 as an unknown source.
Blast radiusA leaked secret can only send to its one source. It cannot read anything.
RecoveryRotate a URL token and the old one stops at once; revoke a signing secret the same way. Other sources are untouched.
AbuseA source that keeps flooding past its limit is paused for 15 minutes. A secret being abused can be revoked by Litehook.
DeliveryPush payloads are encrypted for each device, so push services can't read them (RFC 8291).
LogsRequest paths, which can contain secrets, are never logged. The client IP address, method and status are.

Sources, IDs and secrets

Every sender gets its own source. A source has an ID and a secret:

  • Source ID: 72 random bits, URL-safe. It says which source a request is for. It is not a secret and is safe to share.
  • Signing secret (signed sources, the default): 256 random bits, shown as whsec_ and base64. The sender uses it to sign each request; it never travels with the request. Litehook needs it to check signatures, so it is stored encrypted with AES-256-GCM under keys kept outside the database. A source can hold up to five active signing secrets, so you can add a new one, move your sender over, then revoke the old one.
  • URL token (sources created with Signed requests off): 32 bytes (256 bits) from the operating system's cryptographically secure random generator, encoded as 43 URL-safe characters. It travels in the URL or an Authorization header, and Litehook stores only its SHA-256 hash.

Either secret is shown once, when you create the source or a new secret, and never shown again. If you lose it, make a new one.

How a request is authenticated

A signed source accepts events at /api/v1/sources/SOURCE_ID/events only. Litehook checks the Standard Webhooks signature over the exact bytes received against each active signing secret, refuses timestamps more than five minutes away (a source can allow up to 15), and does not store a repeated webhook-id twice.

Any source can limit senders to listed IP addresses and check each body against a schema.

A URL token source accepts events at two source addresses. Both check the token the same way.

FormWhere the secret goesUse it when
/api/ingest/SOURCE_IDAuthorization: Bearer SECRET headerYou control the code. Preferred.
/hook/SOURCE_ID/SECRETIn the URL pathThe sender only accepts a URL, like Coolify.

For every request to a URL token source, Litehook:

  1. Rejects a malformed URL with 404, and a source ID of the wrong shape with the same 401 as an unknown source.
  2. Looks up the source, which must be active and belong to a workspace that is not deleted.
  3. Hashes the presented secret with SHA-256 and compares it to the stored hash with a constant-time comparison, so timing reveals nothing about how close a guess was.
  4. Returns the same 401 Invalid source token whether the source is unknown, disabled, deleted or the secret is wrong.
  5. Refuses a paused workspace, or an IP address not on the source's allowlist, with 403, then applies the source's rate limit.

With 256 bits of randomness, guessing a secret is not practical.

Transport

  • Use each source address exactly as given. A plain http:// request exposes the secret before any redirect.
  • Litehook's HTTPS responses include Strict-Transport-Security for the domain and every workspace subdomain, so browsers keep to HTTPS after the first visit.
  • The /hook address carries the secret in the path. Litehook never logs request paths, only the client IP address, method and status. Tools on your side, such as a CI log or a proxy, might record URLs, so prefer the header form where you can.

What Litehook accepts

CheckRule
Content typeapplication/json for URL token sources; JSON, plain text or form data for signed sources, as the source allows. Otherwise 415.
SizeAt most 64 KiB, counted as the body arrives whatever Content-Length says, otherwise 413.
ShapeURL token sources: a JSON object; level is one of four values; text fields must be strings.
LengthTitles are cut to 120 characters and messages to 2,000. A group over 80 characters or an idempotency_key over 120 is refused with 400. An Idempotency-Key header can be 200.
Rate300 events a minute per source and 1,200 per workspace, shared across all our servers. One IP address gets 30 failed attempts and 1,200 requests a minute.
VolumeA monthly allowance per workspace; past one and a half times it, 429.
DuplicatesA repeated webhook-id or Idempotency-Key (7 days) or idempotency_key is acknowledged, not stored. A different body gets 409.

Content you send is always shown as plain text in the app, never as HTML, and the app runs under a strict Content Security Policy that allows no inline or third-party scripts.

Storage and durability

Litehook answers 202 only after the event, its device jobs and usage count are saved in one transaction. Nothing accepted is lost on restart.

Events are stored in our PostgreSQL database. Each event body, and each request body waiting to be forwarded, is encrypted at rest with AES-256-GCM under keys kept outside the database. A notification's title and message are stored readable so your inbox can show them, so never put passwords, keys or personal data in a notification. Builder traces and chosen headers are readable for up to 7 days.

Delivery to your devices

  • Each notification is encrypted separately for each device with the keys the device created, using Web Push message encryption (RFC 8291, aes128gcm). Apple, Google and Mozilla's push services carry it but cannot read it.
  • Litehook identifies itself to push services with a signed VAPID token (RFC 8292, ES256).
  • The encrypted payload holds the title, message, level (with its iOS interruption level), source name and category, event number, source ID and group.
  • A device belongs to one person at a time. Signing out detaches it, and moving it to another account drops anything still queued for the previous one.

Accounts and workspaces

  • You sign in with a single-use email link, valid for 15 minutes, or with a passkey. Litehook stores no passwords.
  • Each workspace has its own subdomain and its own __Host- session cookie, which is HttpOnly, Secure, SameSite=Strict and never shared with other subdomains. Every change must come from your workspace's own origin.
  • Every query is limited to your workspace. Another customer cannot see or send to your sources.
  • Changing your email sends a single-use link to the new address, valid for 15 minutes; the email changes only when it is used, and the old address is told about the change.
  • Sign out everywhere, in Settings under Sign-in and security, ends every session and detaches every device. Close account disables sign-in and webhooks immediately; if you own the workspace, it closes alongside your sources and configuration. Accounts are soft deleted, and support can restore one within 30 days.
  • Sign-in links, sign-in codes, sessions and URL tokens are stored only as SHA-256 hashes. The sign-in email itself waits in a short queue and is deleted once sent or after 15 minutes.

Abuse protection

  • Cool-off. A source refused over 300 times in ten minutes for exceeding 300 events a minute is paused for 15 minutes. During the pause it answers 429 with a Retry-After header, and nothing is queued up. A notice in your inbox tells you it happened, so you can look for a loop or a runaway job.
  • Revocation for abuse. If a source is used for abuse, Litehook may revoke its secrets. Your inbox explains why; create a new one in the source's Settings (Rotate token or New secret).
  • Wrong secrets. Requests with a wrong secret never count against the real sender's limits, so nobody can pause your source by guessing at it.
  • Accounts and email. Each IP address can create three accounts a UTC day. Sign-in and email-change emails share a service-wide hourly ceiling; past it, new requests get 429 until the hour ends.
  • Workspace actions. A workspace holds up to 20 notification devices. Each session or API token can create 20 sources, rotate secrets 20 times and send 60 test notifications an hour; past that, the action answers 429 with a Retry-After header.

If a secret leaks

Someone with a source's secret can send notifications to that one source. They cannot read your events, reach other sources or sign in. Only you can create a working replacement. To close it:

  1. Open Sources, select the source and open its Settings tab. For a URL token source choose Rotate token; the old token stops working immediately. For a signed source choose New secret with Revoke the current secret now.
  2. Put the new secret where your sender reads it.
  3. If you do not need the source any more, turn Receive events off instead. Its past notifications stay in your inbox. Delete source stops its source address at once and removes the source from your workspace for good; its event history stays until retention removes it, and its notifications go too if you choose.

Only you can create a new secret. If you lose account access, support can restore it so you can rotate the secret.

Good practice

  • Use one source per sender, so you can rotate one without touching the others.
  • Keep secrets in environment variables or your platform's secret store, never in code or screenshots.
  • Prefer signed sources. For a URL token source, prefer the Authorization header over the URL form.
  • Send a group for repeating alerts and an idempotency_key when your sender retries.
  • Treat Litehook as a notice, not a vault: describe what happened, not the sensitive details.

Reporting a problem

If you think you have found a security issue, contact us using the details in /.well-known/security.txt, the standard location for security contacts (RFC 9116). Please give us a chance to fix it before sharing it publicly.