JustFlows

Documentation is English-only for now. The rest of the site follows your language.

Webhooks

Outgoing signed HTTP callbacks: pick events, receive an HMAC-signed JSON POST, verify the signature, and retry failed deliveries.

6 min read

Admin → Webhooks (/admin/webhooks, administrators). Register an endpoint with a name, an HTTPS URL, and the events it should receive. Justflows then POSTs a JSON body to that URL whenever one of those events fires. Endpoints, events, and the signing secret are managed through /api/webhooks.

Events

Twenty core events are available; a plugin can add its own through the webhook.eventTypes filter (see Hooks).

  • content.created / updated / published / unpublished / deleted
  • media.uploaded / media.deleted
  • user.created / updated / deleted
  • auth.login / auth.logout
  • plugin.installed / activated / deactivated / uninstalled
  • theme.installed / theme.activated
  • core.updated

Signature

Each endpoint has a secret shown once on creation and re-issued by POST /api/webhooks/:id/rotate-secret (prefix whsec_). Every request carries X-Justflows-Signature: sha256=<hex>, an HMAC-SHA256 over <timestamp>.<body>. Recompute it with your secret and compare in constant time; reject old timestamps to stop replay.

Verify a delivery (Node)
ts
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret: string, header: string, timestamp: string, body: string) {
  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${body}`)
    .digest("hex");
  const got = header.replace(/^sha256=/, "");
  return (
    got.length === expected.length &&
    timingSafeEqual(Buffer.from(got), Buffer.from(expected))
  );
}

Delivery and retries

  • The payload is a JSON object with the event name and its data, capped at 256 KB. A plugin can reshape it through the webhook.payload filter.
  • A background job drains the queue every minute. A non-2xx response or timeout is retried up to 5 times with backoff.
  • Deliveries interrupted by a deploy or crash are re-queued on boot, so a callback is never stranded.
  • GET /api/webhooks/deliveries/history returns the last 100 attempts (event, status, response code, error). POST /api/webhooks/deliveries/:id/redeliver re-queues one.

Endpoint URLs are validated

The target URL is checked before it is saved: private, loopback, and link-local addresses are rejected so a webhook cannot be pointed at internal services.