Webhooks

A webhook makes LVNG send an HTTP POST to your server when something happens in your workspace. Workspace owners and admins add them in Settings > Developer > Webhooks, or with POST /api/v2/developer/webhooks.

Events LVNG sends

EventWhen
message.createdA message was posted in a channel that every member of the workspace can read. Messages in private channels and direct messages are not sent.
workflow.completedA workflow run finished successfully, whether it was started from the app, through the API or by a schedule.

Accepted, but not sent yet: agent.completed, credit.low, swarm.spawn, knowledge.indexed. You can subscribe to these events, but LVNG does not send them today. Any other event name is refused with 400.

The Test button (or POST /api/v2/developer/webhooks/:id/test) sends one test.ping event with empty data.

Payload Format

Every delivery is a JSON body with the event name, a timestamp and a data object. Ids are plain UUIDs. A workflow delivery carries the run's ids and timing, not its inputs or outputs.

message.created
{
  "event": "message.created",
  "timestamp": "2026-10-13T10:30:00.512Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "workspace_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
    "channel_id": "660e8400-e29b-41d4-a716-446655440000",
    "channel_name": "general",
    "parent_message_id": null,
    "sender_id": "770e8400-e29b-41d4-a716-446655440000",
    "ai_twin_id": null,
    "content": "Hello from LVNG!",
    "content_type": "text",
    "attachment_count": 0,
    "created_at": "2026-10-13T10:30:00.000Z"
  }
}
workflow.completed
{
  "event": "workflow.completed",
  "timestamp": "2026-10-13T10:31:12.004Z",
  "data": {
    "workspace_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
    "run_id": "880e8400-e29b-41d4-a716-446655440000",
    "workflow_id": "990e8400-e29b-41d4-a716-446655440000",
    "workflow_name": "Weekly report",
    "status": "completed",
    "started_at": "2026-10-13T10:30:41.000Z",
    "finished_at": "2026-10-13T10:31:11.000Z",
    "duration_ms": 30000
  }
}

Verifying Signatures

Each request carries an X-LVNG-Signature header: the hex HMAC-SHA256 of the raw request body, keyed with the webhook's signing secret. The secret is shown once, when you create the webhook. The X-LVNG-Event header names the event.

Signature verification
const crypto = require('crypto');
const express = require('express');

const app = express();

// Sign-check the raw bytes LVNG sent, before parsing them.
app.post('/webhooks/lvng', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.get('x-lvng-signature') || '';
  const expected = crypto
    .createHmac('sha256', process.env.LVNG_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  const valid = signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  if (!valid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event, data } = JSON.parse(req.body.toString('utf8'));
  console.log('Received event:', event, data);

  res.status(200).json({ received: true });
});

Delivery

Respond within 10 seconds

LVNG waits 10 seconds for an answer. Return a 2xx status, then do slow work after you answer.

One attempt, no retries

Each event is sent once. Every attempt, with its status code and time taken, is listed by GET /api/v2/developer/webhooks/:id/deliveries.

Verify signatures

Always check the X-LVNG-Signature header before acting on an event.

Use a public HTTPS address

The address must be a public http or https URL; private and internal addresses are refused. Use HTTPS so the content is encrypted on the way.