Local development

Webhook connectors need a public HTTPS URL. During development, use the standalone calmlens CLI: it reads your project's events with your API key and forwards each one to a local URL, with the same body, headers and signature as a real webhook delivery. It works with every SDK.

Install the CLI

On macOS or Linux, install through Homebrew:

brew tap censkh/calmlens https://github.com/Censkh/CalmLens
brew install calmlens

For Windows, or without Homebrew, download the archive for your platform from CLI releases , extract it and put calmlens (or calmlens.exe) on your PATH. These executables include their runtime; Node, npm and Bun are not required. Check the archive against the release's SHA256SUMS file.

With Node.js 22 or later, you can also install with npm install -g calmlens, or run commands with npx calmlens.

Forward events locally

calmlens listen --forward-to http://localhost:3000/api/webhooks/calmlens --env-file .env.local

The CLI reads CALMLENS_API_KEY, CALMLENS_PROJECT_ID and CALMLENS_WEBHOOK_SECRET from the environment or from --env-file. Without a secret it prints a generated one to put in your app. Use --events asset.classified,asset.flagged to forward only some event types.

To create events without uploading your own content, run calmlens trigger. It moderates a harmless test input that CalmLens classifies as harassment; trigger clean sends text that should be approved. Each trigger counts as one upload.

The CLI only forwards events created after it starts, and attempts each delivery once; it doesn't use the webhook retry schedule, and its deliveries don't appear in the portal. It reads GET /projects/:projectId/events, which you can also call from your own tools with client.events.list().

Read the events feed

Prefer webhook connectors in production. The events feed suits local development, scripts and backfills, and powers calmlens listen. Call it without after to get a cursor for the current time, then pass the returned cursor back to receive only newer events. When a response has hasMore: true, request again straight away.

let { cursor } = await client.events.list();

setInterval(async () => {
  const page = await client.events.list({ after: cursor });
  for (const event of page.events) console.log(event.type, event.asset?.status);
  cursor = page.cursor;
}, 2000);

Signing in

Signing out

Cookies on CalmLens

We use essential cookies to keep you signed in and the site secure. With your permission, we'll also use functional cookies to remember preferences such as your layout and documentation language, and to offer Google One Tap sign-in. We don't use analytics or advertising cookies. Manage cookies