Skip to content

Webhooks

Webhooks let Lychee call an external HTTP endpoint of yours whenever a photo is added, moved, or deleted, so another system can react to changes in the gallery without polling it.

They are off by default. Set WEBHOOK_ENABLED=true in the .env file to make the Webhooks entry appear in the admin panel; without it the page and its endpoints stay hidden. Only administrators can see or manage webhooks.

EventFires when
photo.addA photo is added to the gallery
photo.moveA photo is moved to another album
photo.deleteA photo is deleted

One webhook subscribes to exactly one event. Subscribe to several events by creating several webhooks.

Each webhook carries:

  • a name, for your own reference;
  • the event it listens to;
  • the HTTP method: GET, POST, PUT, PATCH, or DELETE;
  • the target URL;
  • the payload format: json or query_string;
  • an enabled switch, so a webhook can be parked without being deleted;
  • an optional secret and the header it is sent in, defaulting to X-Webhook-Secret.

Every request is sent with User-Agent: Lychee/Webhooks and an X-Lychee-Event header naming the event.

The payload is opt-in field by field, so you only ship what the receiving end actually needs:

FieldContents
photo_idThe photo’s identifier
album_idThe identifier of the album it belongs to
titleThe photo’s title
size_variantsA list of {type, url} entries for the photo’s generated variants

Fields you did not select are omitted from the payload entirely rather than sent as null. size_variants can additionally be narrowed to a chosen subset of variant types; leaving the selection empty sends them all.

With the json format the payload is sent as a JSON request body, whatever the HTTP method. With query_string it goes into the URL’s query parameters instead, and the size-variant URLs are base64-encoded so they survive the encoding.

  • Requests are dispatched asynchronously, as queued jobs, so a slow endpoint never delays an upload. A queue worker has to be running for them to actually go out.
  • There is no automatic retry: each webhook is attempted once. A non-2xx response or a transport error is logged at ERROR level and discarded; a success is logged at DEBUG level.
  • WEBHOOK_TIMEOUT_SECONDS (default 10) caps how long Lychee waits for the endpoint to answer.

Because failures are dropped rather than retried, treat webhooks as best-effort notifications, not as a guaranteed delivery channel.

Terminal window
php artisan lychee:webhook-test <webhook-id>

sends a test request to the configured endpoint using the same dispatch path as a real event, and tells you to check storage/logs/laravel.log for the outcome. The command refuses to run while WEBHOOK_ENABLED is not set.

VariableDescriptionDefault
WEBHOOK_ENABLEDMaster toggle for the whole feature.false
WEBHOOK_TIMEOUT_SECONDSSeconds to wait for the endpoint before giving up.10

Both are read at boot, so a Docker deployment needs a container restart after changing them.