Skip to main content
Webhooks let Lucent push events to your service in real time, instead of you polling the Data API. When an event fires — for example, a new issue is created — Lucent makes a signed POST to the URL you register, with a JSON body describing the event.

Available events

More event types (issue.status_changed, issue.recurred, signal.matched, insight.generated) are on the roadmap.

Register an endpoint

Webhooks are configured per organization, in Organization → Webhooks.
1

Open the Webhooks section

In the dashboard, go to Organization → Webhooks and click Add endpoint.
2

Set the URL and events

Enter:
  • a name (max 64 characters)
  • the URL Lucent should POST to (HTTPS, max 2048 characters)
  • the events you want delivered to this endpoint
3

Copy the signing secret

On save, Lucent shows the signing secret once. It looks like whsec_…. Store it somewhere safe (a secret manager, your .env). The dashboard only ever shows the prefix (whsec_xxxx…) again — there is no way to retrieve the full secret later. If you lose it, revoke the endpoint and create a new one.
4

Send a test event

Use Send test to deliver a synthetic event of the type you chose. Test events carry the Lucent-Webhook-Test: 1 header so you can branch on them in your handler.

Limits

  • Up to 3 active endpoints per organization.
  • Up to 5 endpoint creations per hour per organization.
  • Test event sends are rate-limited per organization and per endpoint.
Webhooks are available on paid plans.

Payload shape

Every delivery is a single JSON object with stable top-level fields:

issue.created

Headers

Verify the signature

Lucent signs every delivery using a Stripe-compatible scheme: HMAC-SHA256 over ${timestamp}.${rawBody}, using your endpoint’s signing secret as the key. The Lucent-Signature header is a comma-separated key/value list:
  • t — Unix timestamp (seconds) of when Lucent generated the signature.
  • v1 — hex-encoded HMAC-SHA256.
To verify:
  1. Read the raw request body. Sign the bytes Lucent sent — re-serializing parsed JSON will not match.
  2. Recompute HMAC_SHA256(secret, "${t}.${rawBody}").
  3. Compare with v1 in constant time.
  4. Reject the request if t is more than 5 minutes from your server clock — this prevents replay of an old, valid request.
Read the raw request body before any JSON parsing or middleware that might rewrite it. In Express, mount express.raw({ type: "application/json" }) for the webhook route. In Next.js Route Handlers, use await request.text().

Retries and idempotency

  • Delivery has a 10-second per-request timeout.
  • Non-2xx responses, timeouts, and network errors trigger a retry — up to 3 retries with exponential backoff after the initial attempt.
  • The same Lucent-Webhook-Id is sent on every attempt. Persist it on success and treat duplicate ids as no-ops.
  • Lucent recomputes the signature on each retry, so t (and the Lucent-Signature value) will differ between attempts. The body bytes and Lucent-Webhook-Id stay the same.
  • Because the timeout is 10 seconds, your handler should acknowledge fast: validate the signature, enqueue the work, and return 200. Do the actual processing asynchronously.

Security model

Webhook URLs go through several checks before every delivery, not just at creation:
  • HTTPS only. Plain http:// URLs are rejected. URLs with user:pass@ credentials are also rejected — fetch would silently send them as Basic auth.
  • Public destinations only. The hostname is resolved fresh on every delivery and its IPs are checked against ipaddr.js ranges. Loopback, private, link-local, and other non-unicast addresses are rejected. This defends against DNS rebinding (an attacker-controlled hostname pointing at 127.0.0.1 between checks).
  • Service ports are blocked. Common service ports — SSH (22), SMTP (25), Postgres (5432), MySQL (3306), Redis (6379), Mongo (27017), Elasticsearch (9200/9300), Memcached (11211), Docker (2375/2376), and others — are rejected so webhooks can’t be used to probe internal infrastructure.
  • Redirects are not followed. Set the final URL on your endpoint directly.
If a delivery is blocked for any of these reasons, the endpoint’s last delivery status is recorded as url_unsafe and no request leaves Lucent.

Endpoint management

Each endpoint surfaces, in Organization → Webhooks:
  • last delivery time and HTTP status
  • last delivery error label, if any (bad_status:NNN, timeout, tls_error, network_error, url_unsafe)
  • a Send test button
  • a Revoke button — revoked endpoints stop receiving deliveries immediately and free a slot against the per-org cap.
The full signing secret is shown only at creation. The dashboard always shows the prefix (whsec_xxxx…) so you can tell endpoints apart without exposing the secret.