Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.envless.cloud/llms.txt

Use this file to discover all available pages before exploring further.

Every webhook request is signed with HMAC-SHA256 using the Standard Webhooks specification. Verifying the signature on every request is mandatory in production.

Headers

HeaderPurpose
webhook-idUUID of the underlying WorkspaceEvent. Use this to dedupe at-least-once deliveries.
webhook-timestampUnix epoch seconds when the request was signed. Reject if drift > 5 minutes.
webhook-signaturev1,<base64 HMAC-SHA256> — the signature over ${id}.${timestamp}.${body}.
Envless-EventThe event type (e.g. project.created) — convenience, also in the body.
Envless-DeliveryUUID of this specific delivery attempt — useful for support requests.

Verifying with the official library

import { Webhook } from 'standardwebhooks'

const secret = process.env.ENVLESS_WEBHOOK_SECRET! // e.g. whsec_abc123...
const webhook = new Webhook(Buffer.from(secret, 'utf-8').toString('base64'))

app.post('/webhooks/envless', (req, res) => {
    try {
        const event = webhook.verify(req.rawBody, req.headers)
        // handle event.type and event.data
        res.status(200).end()
    } catch {
        res.status(401).end()
    }
})

Verifying manually

import { createHmac, timingSafeEqual } from 'crypto'

const signed = `${headers['webhook-id']}.${headers['webhook-timestamp']}.${rawBody}`
const expected = createHmac('sha256', secret).update(signed).digest('base64')
const provided = headers['webhook-signature']?.split(',')[1] ?? ''
const valid = expected.length === provided.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(provided))

Idempotency

Deliveries are at-least-once. Track processed webhook-id values in a key-value store with a TTL (24h is plenty given our retry policy maxes out at ~45 minutes) and skip duplicates.

Rotating the secret

When you rotate from the dashboard:
  1. Envless generates a new secret.
  2. The new secret is shown once in the rotate modal — copy it before closing.
  3. The old secret stops working immediately. There’s no grace period.
  4. Update your receiver and restart it.
If you need a grace-period rotation (dual secrets), file an issue — it’s a planned enhancement.

What to return

Respond with any 2xx status to acknowledge receipt. Non-2xx (or no response within 10 seconds) is treated as a failure and triggers the retry policy. Don’t perform heavy work synchronously — accept the request, queue it, and return 200 fast.