Webhooks

The Content API fires outbound signed POST notifications when a discovered resource is created, updated, or deleted. Receivers verify an HMAC signature over the body and use the event to drive downstream caches, search indexes, or previews. Nothing is pulled; the platform pushes.

Endpoints

Two sources of targets, both signed the same way:

  • Named endpoints — managed on the panel's API settings page (ManageWebhooks). Each row stores a name, URL, the event list it subscribes to, and an is_active toggle. On creation a signing secret is minted (whsec_…) and shown once; it is stored encrypted and never displayed again.
  • Env default — set WEBHOOK_URL (and optionally WEBHOOK_SECRET) to fan every event out to a single default target, independent of the named-endpoint table.
WEBHOOK_URL=https://example.com/hooks/edge
WEBHOOK_SECRET=whsec_shared_rotation_key

Signing

Each delivery is a JSON POST with two custom headers:

  • X-Webhook-Event — the event name (e.g. post.created).
  • X-Webhook-Signaturesha256=<hex> where the hex is hash_hmac('sha256', $rawBody, $secret).

Verify by recomputing the HMAC over the raw request body (not a re-serialized copy) and comparing in constant time. Empty secret skips the signature header, so always set one in production.

Events

Events are derived from discovered panel resources — the same discovery that drives REST and API keys. Adding a Filament resource automatically produces <singular>.created, <singular>.updated, and <singular>.deleted; publishable resources also emit <singular>.published. A webhook.test event is available from each endpoint's "Send test" action.

The payload is resource-shaped: type, id, title, slug, path, published, url, and actor_id (the panel user who triggered the change).

Delivery log

Every attempt — success or failure — writes one row to the deliveries table via WebhookDelivery:

Column Meaning
event Event name dispatched
url Target URL hit
success Boolean, HTTP 2xx
status_code Response status, null on connection error
error Truncated response body or exception message
duration_ms Round-trip time

Inspect the log on the API settings page to debug 4xx/5xx responses, timeouts, or DNS failures. A failed POST still logs a red row, so the log is the source of truth for "did it arrive".

sequenceDiagram
    participant Panel as Panel CRUD
    participant Obs as Model Observer
    participant Disp as Webhooks::dispatch
    participant Job as DeliverWebhookJob
    participant Recv as Receiver
    participant Log as WebhookDelivery
    Panel->>Obs: created/updated/deleted
    Obs->>Disp: event + payload
    Disp->>Job: per active endpoint + env URL
    Job->>Recv: POST JSON + X-Webhook-Signature
    Recv-->>Job: HTTP status
    Job->>Log: insert (success, status_code, error, duration_ms)

See also

Built by Qcentic