Webhooks
Lazynext sends signed POSTs to your endpoint when events fire. Each delivery is signed, retried on transient failure, and includes the headers you need to verify it.
Headers
POST /your-endpoint HTTP/1.1
Content-Type: application/json
X-Lazynext-Event: decision.created
X-Lazynext-Delivery: <uuid>
X-Lazynext-Timestamp: 1745842800
X-Lazynext-Signature: sha256=...Verifying a delivery (Node.js)
Compute HMAC-SHA256 over <timestamp>.<raw-body> using your endpoint's shared secret. Compare with crypto.timingSafeEqual to avoid timing attacks. Reject deliveries older than 5 minutes (replay protection).
import crypto from 'node:crypto'
export function verifyLazynextWebhook(
rawBody: string,
headers: { signature: string; timestamp: string },
secret: string,
): boolean {
// 1. Replay protection — reject deliveries older than 5 minutes.
const ts = Number(headers.timestamp)
if (!Number.isFinite(ts)) return false
if (Math.abs(Date.now() / 1000 - ts) > 300) return false
// 2. Strip the "sha256=" prefix.
const expected = headers.signature.replace(/^sha256=/, '')
// 3. Compute the signature over <timestamp>.<rawBody>.
const actual = crypto
.createHmac('sha256', secret)
.update(`${ts}.${rawBody}`)
.digest('hex')
// 4. Constant-time compare.
const a = Buffer.from(actual, 'hex')
const b = Buffer.from(expected, 'hex')
if (a.length !== b.length) return false
return crypto.timingSafeEqual(a, b)
}Use the raw request body, not a JSON-parsed copy — re-serializing reorders keys and breaks the signature.
Retry policy
On 2xx we mark the delivery successful. On 4xx (other than 408 / 429) we stop retrying — your endpoint told us the payload is bad. On 5xx, 408, 429, or a network timeout we retry with exponential backoff:
- 30 seconds
- 2 minutes
- 10 minutes
- 1 hour
- 6 hours
After 5 attempts the delivery is marked failed. You can replay any failed delivery from Settings → Webhooks.
Idempotency
Use X-Lazynext-Delivery as your dedup key. We may deliver the same delivery id twice in rare cases (network mid-flight, aggressive retry). Your handler must be idempotent — store the delivery id and skip if you've seen it.
Events shipping in v1
decision.createddecision.outcome_loggedworkspace.member_addedworkspace.member_removedbilling.plan_changed