Recipe: post runs into Lark or Slack

Vuon doesn't integrate with every chat product — instead, a tiny bridge you host turns webhook events into messages for whatever your team uses. This recipe forwards run results to a Lark custom-bot URL; the Slack variant differs by two lines.

Prerequisites

  • A Lark custom bot in your target group chat (Group settings → Bots → Add bot → Custom bot). Copy its webhook URL, e.g. https://open.larksuite.com/open-apis/bot/v2/hook/.... For Slack, create an incoming webhook instead.
  • A Vuon webhook endpoint pointing at your bridge, and its signing secret.

The bridge

With verifyWebhook from Verifying signatures in scope:

bridge.ts

import express from 'express';

const app = express();
const LARK_BOT_URL = process.env.LARK_BOT_URL!;

app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const rawBody = req.body.toString('utf8');
  const ok = verifyWebhook({
    secret: process.env.VUON_WEBHOOK_SECRET!,
    rawBody,
    timestampHeader: req.header('vuon-webhook-timestamp') ?? '',
    signatureHeader: req.header('vuon-webhook-signature') ?? '',
  });
  if (!ok) {
    return res.status(401).send('invalid signature');
  }

  const event = JSON.parse(rawBody);
  const { title, summary, imageUrl, chatUrl, failureReason } = event.data;
  const lines =
    event.type === 'automation.run.completed'
      ? [`✅ ${title ?? 'Automation run'} finished`, summary, imageUrl, chatUrl]
      : [`❌ ${title ?? 'Automation run'} failed`, failureReason, chatUrl];

  await fetch(LARK_BOT_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      msg_type: 'text',
      content: { text: lines.filter(Boolean).join('\n') },
    }),
  });

  res.status(200).send('ok');
});

app.listen(3000);

Slack variant

Slack incoming webhooks take { "text": "..." } instead of Lark's msg_type/content envelope:

Slack payload

await fetch(SLACK_WEBHOOK_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: lines.filter(Boolean).join('\n') }),
});

Tips

  • Respond 200 before slow work. Vuon times out after 10 seconds. If your bridge does more than a single forward call, acknowledge first and process in the background.
  • Deduplicate on vuon-webhook-id if a duplicate message in chat would be disruptive — delivery is at-least-once.
  • Lark unfurls imageUrl and chatUrl automatically in most clients; for richer layouts, switch msg_type to interactive and build a card.

Was this page helpful?