Interacting with Verkada webhooks and APIs have never been easier with this tool. Fuse together custom pipelines that allow you to push Verkada's API to its full potential.
Self-hosted, Verkada-flavored workflow automation — a visual router for webhooks and API events. Think Zapier/n8/make.com, but built around the Verkada API surface.
- 📥 Webhook Explorer — catch any Verkada webhook at
/hooks/*, auto-classify into family (camera / access / lpr / sensor / intercom), auto-detect new orgs on first sight - 🎨 Visual flow editor — drag-and-drop canvas (React Flow) for event-driven automations. Conditions, branches, per-step ▶ Run button for testing
- 🎥 Gemini video analysis — pull a historical clip from any Verkada camera at trigger time, send to Gemini (2.5 / 3.x Flash or Pro), get AI summary back. Or analyze a single live frame for ~10× cost savings
- 🚪 Verkada actions — unlock doors, activate/release Access scenarios (Lockdown / Evacuate / Shelter), post Helix events (schema-aware attribute validation), or call any cataloged endpoint generically
- 🧩 Starter templates — one-click flows for the common jobs (animal/breed detection, camera-health checks, door-obstruction sweeps, shelf-stock, OCR unlock, POI lockdown, live weather), each pre-wired with its Helix event type
- 📚 API catalog — auto-syncs every Verkada OpenAPI spec every 4 hours, generates structured request forms for path / query / body params on every endpoint
- ⏰ Triggers — Verkada webhooks + scheduled jobs (interval / daily / weekly)
- 🌦️ Beyond Verkada + Gemini — pluggable connections (e.g. OpenWeatherMap) let a scheduled flow pull external data and post it to Helix too
- 🧪 Workbench — one-shot Gemini test page. Pick a camera, write a prompt, optionally chain a Helix post — without building a full flow first. "Run it back" to rehydrate any past test
- 📊 Stats & cost — ingest counters (24h / 7d / 30d), top event types with Webhook Explorer drill-down, Gemini spend tracking per model, real-time server load (CPU / memory / disk)
- 🌍 Public URLs built-in — two deploy modes: quick mode (free TryCloudflare URL, zero Cloudflare setup) and lab mode (named tunnel on your own domain). URL auto-displayed in the UI banner
- 🔐 Secrets at rest — Fernet encryption for stored API keys + signing secrets, HMAC webhook signature verification, sensitive headers redacted before persistence
Every one of these ships as a one-click starter template (Templates tab) — pre-wired with a trigger, the Gemini/Verkada actions, and (where relevant) a Helix event type so the result lands in Verkada Command without extra plumbing. Pick connections, hit enable.
| Template | Trigger | What the flow does |
|---|---|---|
| 🦌 Animal species detection | Motion webhook (objects = animal) | Gemini returns JSON {animal, breed, behavior}; posts all three to a Helix "Animal Watch" event. Command alerts when Animal is "bear". |
| 🩺 Camera FOV health check | Daily schedule | Gemini judges whether a camera's view is impaired (blocked, blurry, mis-aimed); posts Issue / Severity / Reasoning to Helix only when something looks wrong. |
| 🚪 Door obstruction check | Schedule | Gemini checks a door-facing camera for boxes/pallets/clutter blocking egress (people don't count); posts Object Type / Severity / Reasoning to Helix. |
| 📦 Hourly shelf-stock check | Hourly schedule | Gemini scores shelf fullness 0–100; posts Stock Level / Reasoning to Helix. Command alerts floor staff when stock drops. |
| 🔤 OCR-triggered door unlock | Motion webhook | Gemini OCRs a plate / badge / sign; when it matches a target string, the flow unlocks the door and logs the match to Helix as an audit trail. |
| 🚨 POI-triggered lockdown | Verkada person-of-interest webhook | Activates a configured Command Access scenario (Lockdown / Shelter / Evacuate). Swap to a single-door unlock if you'd rather not fire a whole scenario. |
| ⛅ Weather sentry | Schedule (every 30 min) | Pulls live conditions from OpenWeatherMap and posts temp / wind / visibility / etc. to a Helix "Weather" event so ops can see conditions next to a camera and alert on hazards. |
Or skip the templates entirely: the Workbench lets you point Gemini at any camera with a custom prompt ("Do you see a spill on the ground?") and optionally chain a Helix post — no flow required.
This tool can unlock doors, pull live and historical camera footage, post events into Verkada Helix, and call any Verkada API endpoint your key allows. Treat it like the production system it talks to:
- Scope your Verkada API key to least privilege. Only grant permissions for actions your flows actually need — e.g. if you only want Gemini analytics, do not grant door control. The key is encrypted at rest, but a tighter scope is a smaller blast radius if a key leaks or a flow misfires.
- Don't expose the dashboard or backend API to the public internet. A single-password login gates them (set during first-run setup), but it's basic — no MFA, no rate-limit, no IP allow-list. Treat it as one layer; bind these ports to LAN/localhost and use Tailscale or a VPN for remote access. The only thing meant to face the internet is
POST /hooks/verkada. - Always set a webhook signing secret. Without it, anyone who knows your public
/hooks/verkadaURL can forge events — which may trigger real actions (a door-unlock flow, a Helix post on the wrong camera). - Cap your Gemini API spend. Set a daily/monthly budget alert in Google AI Studio so a runaway flow (or a noisy webhook source) can't surprise you with a bill. vFusion's Stats page shows an estimate — Google's billing is the source of truth.
- Understand what Google does with your camera footage. On the Gemini free tier (an AI Studio key with no billing enabled), Google's API terms allow them to use your prompts, uploaded camera frames / video clips, and the returned analysis to "improve their products," and trained human reviewers may see that data. For a physical-security tool that means real footage from your cameras is in scope for Google training data and human review. On the paid tier (billing enabled on the Google Cloud project linked to your AI Studio key — same key, same endpoint), different terms apply: Google doesn't use your prompts or responses to improve products, and there's no human review. Flip to the paid tier before pointing vFusion at production cameras. If you'd rather stay free-tier, restrict the flows you build to test cameras / non-sensitive views.
- Back up the
vfusion_secretsdocker volume. It holds the Fernet master key that encrypts every stored credential. Losing it = losing all of them. - Run on infrastructure you control. Review the code first. Don't point this at production orgs you can't afford to debug.
Line these up before you start. Docker is the only thing you install on the host — Postgres, Redis, Python, Node, and ffmpeg all run inside containers.
- A host to run it on — Linux, macOS, or Windows. Run it always-on for 24/7 webhook capture, or spin it up just for a demo / test session and tear it back down — either works.
- Docker Desktop (or Docker Engine + Compose v2). Verify with
docker --versionanddocker compose version. - A Verkada Command organization with admin access — needed to create the webhook (Admin → API & Integrations → Webhooks) and to generate an API key.
- A Verkada API key — generated in Verkada Command. Used to pull camera footage, post Helix events, unlock doors, and call catalog endpoints.
⚠️ Scope the key to least privilege. A Verkada API key only grants the permissions you pick when you create it. Give vFusion access to exactly what your flows need — and nothing more. For example, if you only want Gemini analysis of camera footage, grant camera/footage access but not door control. The key is stored encrypted at rest, but a tighter scope is a smaller blast radius if a key ever leaks or a flow misfires.
- A Google Gemini API key — free to create at aistudio.google.com. Required only for the Gemini video / still-image analysis actions and the Workbench. Flows that just post Helix events, unlock doors, or call generic API endpoints don't need it. Gemini usage is billed by Google; vFusion tracks an estimated cost. For production cameras, enable billing on the AI Studio key's linked Google Cloud project — see the data-handling bullet in Before you deploy. Free-tier keys let Google use your camera frames + clips + prompts for training, with possible human review.
- A Cloudflare account + a domain on Cloudflare — only for lab mode (a stable webhook URL on your own domain). Quick mode needs neither — it uses a free, ephemeral TryCloudflare URL.
- Tailscale or a VPN — recommended for remote admin access. The dashboard and admin API have no built-in auth, so don't expose them to the public internet.
| Mode | Webhook URL | Best for |
|---|---|---|
| Quick | https://<random>.trycloudflare.com/hooks/verkada — changes on every restart |
Testing, demos, kicking the tires. No Cloudflare account or domain needed. |
| Lab | https://hooks.yourdomain.com/hooks/verkada — stable |
Always-on deploys. Requires a free Cloudflare account + a domain on Cloudflare. |
Both share the same bootstrap below. After that, pick one path.
Same for both modes. If you're skipping ahead to Lab mode, do these steps first.
Download from docker.com/products/docker-desktop (free for personal / small-business use). Open the app once after install to start the engine.
Verify:
docker --version
docker compose versioncd ~
git clone https://github.com/PacketTrace/vFusion.git
cd vFusion
cp .env.example .envHeads up:
.envis a hidden file (leading dot). It won't show up inls, Finder, or a stock VS Code file tree until you toggle hidden files on (ls -aon the CLI,Cmd/Ctrl + Shift + .in Finder, View → Show Hidden Files in your editor). If you don't see it after thecp, that's why — it copied successfully.
That's it for setup. The encryption key (FERNET_KEY) generates itself on first backend boot and persists in the vfusion_secrets docker volume — you don't have to run anything. If you'd rather manage the key yourself (e.g. via 1Password), drop it into .env before starting the stack.
The defaults assume the stack and your browser are on the same machine (localhost). If you'll open the dashboard from a different device — e.g. vFusion runs on a Proxmox / Raspberry Pi / NUC and you browse from your laptop — set these two in .env to the host's LAN IP or hostname so the browser can reach the backend:
VITE_API_BASE=http://<host-ip>:18080 # where the dashboard sends API calls
CORS_ORIGINS=http://<host-ip>:15173 # origin the backend accepts requests from
Use the same <host-ip> (e.g. 192.168.1.50) for both, then reach the dashboard at http://<host-ip>:15173 — not localhost. If you're editing .env before the first docker compose up, you're done — just start the stack normally. If the stack is already running, docker compose up -d --force-recreate frontend backend picks up the new values. Keep these ports on your LAN/VPN; only POST /hooks/verkada should ever face the internet.
⚠ Don't set
PUBLIC_WEBHOOK_BASEto your LAN IP. That env var controls the public webhook URL the dashboard tells you to paste into Verkada Command — Verkada's cloud needs to reach it from the internet, so it has to be a publicly-resolvable URL. In quick mode it's auto-detected from the TryCloudflare tunnel; in lab mode set it to your named-tunnel hostname (e.g.https://hooks.yourdomain.com), not the LAN IP. Leaving it unset is fine — the UI just falls back to the host IP, which is a useful hint but not a working public URL.
⚠ Back up the
vfusion_secretsvolume. It holds the master key that encrypts every stored API key and signing secret. Losing it = losing all your stored credentials.docker run --rm -v vfusion_secrets:/src -v $PWD:/dst alpine tar czf /dst/vfusion-secrets-backup.tar.gz -C /src .exports it to a tarball.
For trying the full Verkada → webhook → flow loop without configuring a domain. Cloudflare hands you a random https://<random-words>.trycloudflare.com URL.
docker compose --profile quick up --build -dFirst build takes ~2–3 min (image pulls + npm install + alembic migrations). Subsequent starts are seconds. Then open http://localhost:15173 — the welcome modal shows a "Stack is healthy" green callout once everything is running (your smoke test that Postgres / Redis / backend / worker / frontend all came up), and the Webhook Explorer banner shows your trycloudflare URL within ~10 seconds. If a service didn't come up, docker compose logs --tail=50 -f <service> is where to look.
Open http://localhost:15173. On first run you'll be asked to set an admin password (single user, hashed with bcrypt — write it down, there's no email recovery). Then the welcome modal walks you through wiring up the webhook — both the Generate signing secret button and your public webhook URL live right in the modal. The signing secret is what lets vFusion verify each inbound webhook actually came from your org (both sides compute HMAC-SHA256 against the same string). Strongly recommended even in quick mode — without it, anyone who finds your trycloudflare URL could forge events.
- In the vFusion welcome modal → click Generate signing secret → click Copy.
- Verkada Command → Admin → API & Integrations → Webhooks → Add:
- Endpoint URL: copy the trycloudflare URL from the welcome modal (it already includes
/hooks/verkada) - Shared secret: paste the string you just generated
- Pick the notification types you want
- Save
- Endpoint URL: copy the trycloudflare URL from the welcome modal (it already includes
- Trigger a webhook — either wait for a real Verkada event to fire, or trigger one yourself (e.g. unlock a door, or walk in front of a camera with a motion/POI rule).
The dashboard auto-unlocks the moment the first webhook arrives — vFusion auto-detects the org and creates a Connection for it. In the Webhook Explorer, the event should show a green ✓ verified badge. To finish, head to Connections, open the auto-created Verkada org, and paste in your Verkada API key so the action steps can act on cameras, doors, and Helix.
Only the exact path POST /hooks/verkada. A Caddy reverse proxy sits between the trycloudflare URL and the backend and returns 404 for anything else (admin API, dashboard, synthetic slugs, wrong HTTP methods). So even if the URL leaks — Slack screenshot, Verkada Command webhook config, etc. — attackers can only POST webhook payloads, same surface as Verkada's cloud has.
The trycloudflare URL changes every time cloudflared restarts. You'd have to re-paste the new URL into Verkada Command after each restart. For something stable, see Lab mode below.
For always-on deploys with a stable URL. Requires a free Cloudflare account + a domain on Cloudflare.
Order of operations: bring the stack up first, then create the Cloudflare tunnel. The Cloudflare UI wants you to verify the tunnel is "Healthy" before saving — that can't happen until the backend container is already running and listening.
Bring up everything except cloudflared so the backend is listening on localhost:18080 while you wire Cloudflare up:
docker compose up --build -dOpen http://localhost:15173 (or http://<host-ip>:15173 for a remote-host setup) and confirm you see the "Stack is healthy" green callout in the welcome modal. That's your smoke test — Postgres, Redis, backend, worker, and frontend are all running. If any service is failing, docker compose logs --tail=50 -f <service> is where to look.
- Sign in to the Cloudflare Zero Trust dashboard.
- Navigate to Networks → Tunnels → Create a tunnel → pick Cloudflared → Next.
- Name the tunnel
vfusion→ Save tunnel. (Cloudflare's UI jumps you straight to the name field — there's no separate "connector type" step in the current dashboard.) - The next screen shows per-OS install commands. You don't need to install anything — our
docker-compose.ymlrunscloudflaredin a container for you. Just copy the token out of one of the install commands — it's the long string after--tokenstarting witheyJhIjoi.... Click Next. - On the Add a public hostname screen (also reachable later under the tunnel's Routes tab → Published application routes):
- Subdomain:
hooks - Domain: pick the domain you added to Cloudflare
- Path: leave blank. (The field now uses regex syntax —
^/hooks/would also work, but blank is simpler and Caddy / the backend already path-filter on their side in quick mode, while lab mode's exposure is locked down by what the tunnel publishes.) - Service → Type:
HTTP→ URL:- If you're running
cloudflaredfrom the bundled docker-compose profile (the normal path):backend:8000 - If you installed
cloudflaredstandalone on the host instead:http://<host-ip>:18080
- If you're running
- Subdomain:
- Save hostname. Your public URL is now
https://hooks.yourdomain.com/hooks/verkada.
⚠ Turn off "Bot Fight Mode" for this domain (Cloudflare → your domain → Security → Bots). It's on by default and silently blocks Verkada's webhook senders as suspected bot traffic, so events never reach the tunnel and never land in the Webhook Explorer. If you want bot protection, leave Bot Fight Mode on but add a WAF Skip rule for the
/hooks/*path so Verkada's POSTs pass through.
cd ~/vFusion
echo "CF_TUNNEL_TOKEN=<paste-token-here>" >> .env
docker compose --profile cloudflared up -dVerify the tunnel connected:
docker compose logs cloudflared | grep -i "registered tunnel connection"You should see 2–4 lines (Cloudflare connects to multiple POPs for redundancy). Back in the Cloudflare dashboard, the tunnel's status should now read Healthy.
Open http://localhost:15173. The welcome modal walks you through it — the Generate signing secret button and your stable webhook URL are both in the modal.
- In the vFusion welcome modal → click Generate signing secret → click Copy.
- Verkada Command → Admin → API & Integrations → Webhooks → Add:
- Endpoint URL:
https://hooks.yourdomain.com/hooks/verkada - Shared secret: paste the string from step 1
- Events: pick the notification types you want, or "all events"
- Save
- Endpoint URL:
- Trigger a webhook — wait for a real Verkada event, or fire one yourself (unlock a door, trip a motion/POI rule in front of a camera).
The first webhook lands in the Webhook Explorer within a couple seconds — vFusion auto-detects the org and the welcome gate dismisses. Head to Connections, open the auto-created Verkada org, and supply:
- Your Verkada API key — generated in Verkada Command
- The webhook signing secret field is already populated with the value from step 1; leave it as-is
Save. The next webhook in the Webhook Explorer should now show a green ✓ verified badge.
vFusion handles credentials with broad permissions on real physical-security infrastructure, so the security model is worth understanding up front. Here's what's in the box, and what isn't.
- Single-user admin password. The dashboard and admin API are gated by a password the operator sets on first run. The password is hashed (not encrypted — hashing is one-way) with bcrypt (cost 12, per-password salt, SHA-256 pre-digest so long passphrases aren't truncated) and stored in
app_settings. It never appears in the UI or any API response. Sessions are HMAC-signed cookies (HttpOnly, 7-day expiry) keyed offSECRET_KEYin.env— rotatingSECRET_KEYinvalidates every existing session at once. - Secrets encrypted at rest. Stored API keys and webhook signing secrets are encrypted with Fernet (AES-128-CBC + HMAC-SHA-256) before hitting Postgres. The encryption key auto-generates on first backend boot and persists in the
vfusion_secretsdocker volume — never committed, never exposed via the UI. If the DB leaks without the volume, the credentials inside stay opaque. (You can override the auto-generated key by settingFERNET_KEYin.env— useful for 1Password / KMS-driven deploys where the secret lives elsewhere.) - Webhook authenticity via HMAC. Every inbound webhook with a configured signing secret runs through
HMAC-SHA-256(secret, body|timestamp)per Verkada's documented scheme, with a 5-minute replay tolerance and constant-time comparison. Mismatches are flagged in the Webhook Explorer as ✗ bad sig. Without a configured secret, webhooks land asunverified— they ingest but you can't prove they came from Verkada. - Public tunnel locked down by path + method. In both quick and lab modes, the only thing reachable through the public URL is
POST /hooks/verkada. Quick mode enforces this with a bundled Caddy reverse proxy (Caddyfile incaddy/). Lab mode enforces it with the Cloudflare-dashboardhooks/*path filter. The admin API, dashboard, and synthetic/hooks/<slug>paths return 404 to the public internet — and the 404 (vs 405) keeps the path itself opaque to scanners. - Generated signing secrets, not user-typed. The Connection form's Generate button produces 48 random bytes from
crypto.getRandomValues(URL-safe base64, ~64 chars). No prompts that tempt users to type "password123." - Sensitive request headers redacted.
Authorization,Cookie,X-API-Key, andX-Verkada-Authare scrubbed before any request body lands in thewebhook_eventstable. Inspecting an event in the Webhook Explorer won't reveal another system's auth headers. - Org auto-creation is UUID-gated. A malformed or all-zero
org_idwon't auto-create aConnectionrow — that prevents synthetic test traffic (smoke-test curls, scanner probes) from polluting the Connections page or accidentally unlocking the onboarding gate. - First-run onboarding gate. On fresh installs the dashboard is gated behind a welcome modal until a real Verkada webhook arrives. A fully-public quick-mode URL doesn't expose configurable flows to anyone who happens to load
localhost:15173first. The modal also has a Skip for now button to open the dashboard early (e.g. to explore in LAN mode) — the skip is persisted server-side, so only do it on a deploy you control. - Retention windows. Captured webhook bodies, downloaded asset images, and run history get swept on a schedule (defaults: 30 days for events, 90 for runs, 1–7 for media). Configurable in Settings → 0 means "keep forever." Reduces the blast radius of a DB compromise.
Be honest about the gaps so you don't deploy assuming things you shouldn't:
- Single-user, basic auth — no MFA, no RBAC. One shared admin password gates everyone; there are no per-user accounts, no MFA, no rate-limit on the login endpoint, and no IP allow-list. Treat the password like a shared API key — long, unique, not committed anywhere. Don't rely on the login alone for public exposure: bind the dashboard and backend port to LAN/localhost, and share access with teammates via Tailscale or a VPN rather than in-app permissions.
- Trust between Verkada and vFusion is webhook-secret-only. With a configured signing secret, every webhook is HMAC-verified. Without one, anyone who knows your public URL can fire fake webhooks at
/hooks/verkada. Always set the signing secret on production deploys. - The Fernet key is the master key. Anyone with read access to both the
vfusion_secretsvolume (orFERNET_KEYenv var, if you've overridden it) and the database can decrypt every stored credential. Treat the volume + env like any other production secret: restrict host-level access, back up the volume, and rotate if a host is compromised.
.env is just plaintext on disk. For anything beyond a casual demo, don't keep real secret values there — render .env from a template at deploy time and keep the actual values in a password manager or KMS.
A common pattern with the 1Password CLI (the same pattern works with AWS Secrets Manager, HashiCorp Vault, etc.):
-
Keep a
.env.tplon the deploy host only (gitignored in this repo), with vault references instead of values:SECRET_KEY=op://YourVault/vFusion/SECRET_KEY FERNET_KEY=op://YourVault/vFusion/FERNET_KEY -
Render
.envat deploy time, then start the stack:export OP_SERVICE_ACCOUNT_TOKEN="$(cat /etc/op-token)" op inject -i .env.tpl -o .env && chmod 600 .env docker compose --profile <quick|cloudflared> up --build -d
Rotating a secret = update the 1Password item once; the next deploy picks it up. Audit trail of every secret read stays in 1Password. The repo only ever sees op:// references.
There's no password-reset email or recovery flow by design — single-user, no email layer. To clear the password and re-trigger the first-run setup wizard, delete its row from app_settings:
docker compose exec postgres psql -U verkada -d vfusion \
-c "DELETE FROM app_settings WHERE key='admin_password_hash';"Refresh the dashboard and you'll be back at the setup screen. (The Reset everything danger-zone button in Settings also clears the password as a side effect, alongside everything else.)
If you keep your .env private, set a signing secret on every Verkada Org connection, and only expose the dashboard over Tailscale/LAN, the public-facing surface is exactly POST /hooks/verkada — same as what Verkada's cloud already sees.
cd ~/vFusion
git pull
docker compose --profile <quick|cloudflared> up --build -dMigrations run automatically on backend boot.
| Service | Host port | Container port | Notes |
|---|---|---|---|
| frontend | 15173 | 5173 | Vite dev server, React + React Flow |
| backend | 18080 | 8000 | FastAPI; runs alembic upgrade head on start |
| worker | — | — | arq worker — runs flow executions and scheduled triggers |
| postgres | — | 5432 | DB vfusion — internal only |
| redis | — | 6379 | execution queue — internal only |
backend/ FastAPI + SQLAlchemy + Alembic + arq
app/api/ route handlers
app/models/ ORM models
app/engine/ action registry, condition operators, template resolver
app/connectors/ Verkada client + Gemini integration
app/worker.py arq worker — flow execution + cron jobs
frontend/ Vite + React + Tailwind + React Flow
src/pages/ one component per top-level route
src/components/ shared UI (JSON viewer, flow nodes, gates, etc.)
Starter templates — one-click flows pre-wired with a trigger, Gemini/Verkada actions, and a matching Helix event type. Filter by tag, glance at the animated flow summary, hit Use.
Visual flow editor — a drag-and-drop canvas for event-driven automations: conditions, branches, and a per-step run button for testing.
Webhook Explorer — every Verkada webhook captured, auto-classified into a family, and HMAC signature-verified.
Workbench — a one-shot Gemini test runner: pick a camera, write a prompt, see the result before committing it to a flow.
Run detail — live phase progress, the frame Gemini saw, the values posted to Helix, and an estimated cost.
Stats & cost — webhook ingest counters (24h / 7d / 30d), webhooks by family, top event types, estimated Gemini spend, and real-time server load.
Gemini's analysis doesn't just sit in the vFusion dashboard. The verkada_helix_event action posts it straight back into Verkada Command as a searchable Helix event — attached to the camera and timestamp. The result lands in Command as an event you can search and filter:
Animal detection — a wildlife camera flags a "bear or coyote", and Verkada Command's notification rules fire a push alert on the Helix event vFusion posted.
Delivery / package summary — a custom prompt describing what Gemini saw at the front door, written straight to Helix.
Text extraction (OCR) — vFusion reads sign, label, and badge text off a short clip…
…and writes it to a Helix attribute in Command.
Beyond cameras — the Weather sentry pulls live conditions from OpenWeatherMap on a schedule and writes temp, humidity, pressure, visibility, and wind into a Helix event — so external data shows up right next to a camera in Command.
vFusion classifies inbound Verkada webhooks into families (camera / access / lpr / sensor / intercom / alarm / credential) using a built-in taxonomy. New webhook types ship from Verkada from time to time and won't be recognized until the table is updated.
The dashboard has an Unrecognized events page (/unrecognized) that groups webhooks vFusion couldn't classify. If you see entries there, please open an issue with the webhook_type and notification_type values (and a sample payload if you can spare one) and the taxonomy will be expanded to cover them.
Why Gemini and not another AI provider?
Gemini is currently the only major LLM with a usable video analysis API — every other provider tops out at still images. vFusion's headline action is sending real Verkada camera clips through an LLM, so Gemini was the natural fit. Other providers (OpenAI, Anthropic, etc.) will land once they ship comparable video capabilities, or sooner for the still-image / text-only actions.
How accurate is the cost estimate?
Usually pretty close, but it's an estimate — not a Google invoice. vFusion captures the input/output token counts from each Gemini response and multiplies by the per-million-token rates in its built-in pricing table (refreshed daily, seeded from Google's published rates). That gets within a few percent of reality most of the time.
What it can miss: cached-token tiers, region-specific pricing, model-specific quirks, anything Google changes between price-table refreshes. The Stats page always labels figures as ~$x.xx estimated — not a Google invoice for that reason.
Set a budget alert in Google AI Studio regardless. It's the only real safety net against a runaway flow (or a noisy webhook source firing the same expensive video-analysis step thousands of times) running up a bill you didn't see coming. Google's billing is the source of truth; vFusion's estimate is just the dashboard view.
Even though it's self-hosted, does my API key or data get shared anywhere?
No — vFusion has no telemetry, no analytics, and never phones home. Your stored API keys are Fernet-encrypted at rest in your local Postgres and only leave the host when one of your flows explicitly calls Verkada or Gemini.
The full data path is: a webhook lands → it's recorded in your Postgres → if a flow fires, the backend makes outbound calls to Verkada's API (api.verkada.com) and/or Google's Gemini API (generativelanguage.googleapis.com) with whatever your flow says to send. That's the entire blast radius:
- Verkada sees the API calls your flows make — same surface that Verkada Command would otherwise call.
- Google sees the prompts, frames, and video clips you send to Gemini, governed by Google's own API terms — and what those terms allow depends on whether your AI Studio key is on the free tier or the paid tier. Free tier (no billing enabled): Google may use your camera frames + clips + prompts to train models, and trained human reviewers may see that data. Paid tier (billing enabled on the linked Google Cloud project — same key, same endpoint): Google doesn't use your prompts or responses to improve products, and there's no human review. See Google's Gemini API Additional Terms for the contract language. For production cameras, flip the project to paid.
- Cloudflare (only if you use a tunnel) sees TLS-encrypted webhook traffic on its way in, the same way any reverse proxy would.
Nothing goes to the maintainer or to any third party.
How well does this application scale?
Honestly — not very well yet. vFusion is early beta. Single Postgres, single Redis, single arq worker, no horizontal scaling, no load testing. For a typical Verkada deployment with a few hundred webhooks per day it's comfortable; for thousands per minute it isn't designed for that today. Bigger-deploy patterns (multi-worker, queue prioritisation, ingest backpressure) will arrive as the project matures.
Where do I file feature requests or bugs?
Open an issue on GitHub: github.com/PacketTrace/vFusion/issues.
- Feature requests — describe the use case: what you'd trigger on, what you want the flow to do, where it'd plug into the existing editor. A small concrete example is worth more than a vague wishlist item.
- Bugs — include the backend log lines around the failure (
docker compose logs backend | tail -100) plus the steps to reproduce. Screenshots of the flow / webhook payload help a lot.
Can I clone this and make my own?
Yes — vFusion is MIT licensed, so you can fork it, modify it, run it commercially, repackage it, whatever you want. The one obligation MIT imposes is keeping the original copyright and license notice (the LICENSE file) in your fork or any redistribution. Crediting Casey Keller / PacketTrace in your README is appreciated but not legally required. If you build something interesting on top, I'd love to hear about it.
Built by Casey Keller (GitHub · LinkedIn) — a Verkada SE. vFusion is a personal project and is not an official Verkada product.










