Webhooks
Receive real-time notifications when project lifecycle events occur. LocalMind
sends HTTPS POST requests with a signed JSON payload to your configured
endpoints. A transactional outbox guarantees that a state change and its
event are written in the same database transaction — so state changed always
implies event emitted, never a phantom or a miss.
Setting up an endpoint
- Go to Project settings → Webhooks.
- Add an HTTPS endpoint URL and pick the events to subscribe to.
- Copy the signing secret (shown once; rotate it any time).
Endpoint URLs are validated against SSRF: only https:// is accepted, and hosts
resolving to private, loopback or link-local ranges (including cloud metadata
IPs) are rejected — both at registration and before every delivery.
Day-one event catalogue
Only events for what really exists today are emitted; product features add more later without touching the delivery machinery.
| Event | Fires when |
|---|---|
project.created / project.updated / project.deleted | A project is created, changed, or soft-deleted |
member.added / member.removed / member.role-changed | Membership changes |
apikey.created / apikey.revoked | An API key is minted or revoked |
Payload & idempotency
Each delivery carries a stable event id. Deliveries are at-least-once, so
deduplicate on that id — retries reuse it. Payloads include an apiVersion
field; breaking schema changes bump the version.
Signature verification
Each request carries an X-Webhook-Signature header in Stripe's format:
t=<unix-seconds>,v1=<hex-hmac>Compute HMAC-SHA256(secret, "<t>.<rawBody>") and compare in constant time.
Reject deliveries whose timestamp is outside a ±5-minute tolerance to defeat
replays.
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}Secret rotation keeps the old and new secret valid during an overlap window so you can migrate without dropping deliveries.
Retries, dead-lettering & replay
Failed deliveries (5xx, network errors, timeouts) retry with exponential backoff
(~1m / 5m / 30m / 2h / 5h / 10h), then are marked dead. 4xx responses
other than 429 are permanent and not retried; a 429 honours Retry-After.
Deliveries never follow redirects, and each attempt re-resolves the host IP
(DNS-rebinding protection).
An endpoint that fails continuously past a threshold is auto-disabled and a notification is sent. You can replay a single delivery or an endpoint's whole failed batch from the dashboard once the receiver is fixed. Delivery logs are retained for 90 days.