vTilt
By roleFoundersKnow what to fix firstMarketersSee what happens after the clickCMOsSee what your budget really buys
By businessSaaSTurn signups into active usersEcommerceSee why shoppers abandon cartsAgenciesProve your work to clientsEnterpriseOne customer record for every team
MeasureWeb AnalyticsFind where buyers leaveSession ReplaySee why they left, not just that they didAdsKnow what paid visitors do nextIntegrationsOne snippet feeds your stack
ThinkPeopleOne profile per customerCustomer MemoryvTilt remembers every visitorSite KnowledgeAnswers and audits from your own pages
ActAsk AIAnswers from real behaviorAI ChatChat that knows the visitorEmail CampaignsEmail from real visits
PricingWhy vTiltDocs
Docs / Phoenix
Getting Started
OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes
Guides
Event forwardingReverse proxyRealtime dashboardSite knowledge
Frontend frameworks
Next.jsNuxt.jsVue.jsReactReact RouterRemixGatsbySvelte / SvelteKitAstroAngularTanStack StartDocusaurus
Backend frameworks
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails
Backend languages
PythonPHPRubyElixirGoJava.NET / C#Rust
Stack guides
Vue + PHP
API Reference
Browser SDK
Script bundlesAutocaptureWeb VitalsSession recordingChat widgetFeature readinessRemote configurationDebug logging
Node SDK
Install & setupCapture, identify & aliasContext & shutdownGlobal properties & opt-outError tracking
MCP server
Guides
OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceSite knowledgeGoogle AdsMeta Ads
Client setup
CursorClaude DesktopVS CodeCodex

Getting Started

OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes

Guides

Event forwardingReverse proxyRealtime dashboardSite knowledge
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails

API Reference

MCP server

On this page

On this page

  • 1. Configure runtime
  • 2. Render the SDK in root.html.heex
  • 3. Server-side capture module
  • 4. LiveView events
  • Next steps
DocsGuidesBackend frameworksPhoenix

Phoenix

Last updated July 22, 2026

Integrate vTilt with Phoenix (Elixir) — render the SDK in root.html.heex, identify on every authenticated request, send server-side events from a HTTPoison/Req helper.

There's no native Elixir SDK yet, but vTilt's wire format is a plain JSON POST so any HTTP client (Req, HTTPoison, Finch) can emit events. The pattern below renders the Browser SDK from your Phoenix root layout and adds a thin Elixir module for server-side captures.

#1. Configure runtime

# config/runtime.exs
config :my_app, :vtilt,
  token: System.fetch_env!("VTILT_TOKEN"),
  host: System.get_env("VTILT_HOST") || "https://www.vtilt.com"
elixir

#2. Render the SDK in root.html.heex

Pass the current user from your :browser plug pipeline (assigns[:current_user]) into the layout, then init the SDK and identify when the user is set.

<!-- lib/my_app_web/components/layouts/root.html.heex -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <script>
      window.__vtilt = <%= raw Jason.encode!(%{
        token: Application.get_env(:my_app, :vtilt)[:token],
        host:  Application.get_env(:my_app, :vtilt)[:host],
        user:  if(@current_user, do: %{id: to_string(@current_user.id), email: @current_user.email}, else: nil)
      }) %>;
    </script>
    <script>
      !(function(t,e){/* ...vTilt loader snippet from Quick start... */})(document, window.vt || []);

      vt.init(window.__vtilt.token, {
        api_host: window.__vtilt.host,
        autocapture: true,
        capture_pageview: true,
        capture_pageleave: true,
      });

      if (window.__vtilt.user) {
        vt.identify(window.__vtilt.user.id, { email: window.__vtilt.user.email });
      }
    </script>
  </head>
  <body>{@inner_content}</body>
</html>
heex

Important

Identifying users in the root layout means every authenticated page load identifies — not just login. See Identify & alias for the full model.

#3. Server-side capture module

Add req (or your preferred HTTP client) to mix.exs:

{:req, "~> 0.5"},
{:jason, "~> 1.4"},
elixir

Then create a small wrapper:

# lib/my_app/vtilt.ex
defmodule MyApp.VTilt do
  require Logger

  def capture(distinct_id, event, properties \\ %{}) do
    cfg = Application.get_env(:my_app, :vtilt)

    Task.start(fn ->
      Req.post(
        cfg[:host] <> "/api/e",
        json: %{
          api_key: cfg[:token],
          event: event,
          distinct_id: distinct_id,
          properties: properties
        },
        receive_timeout: 2_000
      )
      |> case do
        {:ok, _} -> :ok
        {:error, e} -> Logger.warning("vtilt capture failed: #{inspect(e)}")
      end
    end)
  end
end
elixir
# lib/my_app_web/controllers/checkout_controller.ex
defmodule MyAppWeb.CheckoutController do
  use MyAppWeb, :controller

  def complete(conn, _params) do
    user = conn.assigns.current_user
    MyApp.VTilt.capture(to_string(user.id), "purchase_completed", %{amount: 99.99})
    json(conn, %{ok: true})
  end
end
elixir

#4. LiveView events

Inside a LiveView, you can emit server-side events on handle_event/handle_info, or push the SDK call to the browser via push_event:

def handle_event("cta_click", _params, socket) do
  {:noreply,
   socket
   |> push_event("vtilt:capture", %{event: "cta_clicked", properties: %{location: "hero"}})}
end
elixir
// assets/js/app.js
window.addEventListener('phx:vtilt:capture', e => {
  vt.capture(e.detail.event, e.detail.properties)
})
javascript

#Next steps

  • Identify & alias — anonymous → known user merge.
  • Event forwarding — fan events out to GA4, Meta CAPI, PostHog.
  • Reverse proxy — block-resistant ingestion.
  • Common mistakes — integration pitfalls to avoid before you ship.
PreviousLaravelIntegration guidesNextRuby on RailsIntegration guides