Skip to content
Lapis
Esc
navigateopen⌘Jpreview
On this page

Next.js & custom code

Integrate the Lapis pixel in a Next.js, React, or custom stack: layout script, click-ID persistence, and server events.

When you own the code, the integration is three small pieces: the script in your root layout, the click ID persisted wherever your conversion flow starts, and a server event from your backend.

1. Pixel in the root layout

Next.js App Router:

// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://cdn.trylapis.com/pixel/v1/lapis-pixel.js"
          data-key="pk_YOUR_PIXEL_KEY"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

For any other stack, use the plain <script> tag before </body> in your base template.

2. Persist the click ID

Here is a small helper, used at the start of your conversion flow (the signup mutation, or checkout-session creation):

export function getLapisClickId(): string | null {
  if (typeof document === "undefined") return null;
  const cookie = document.cookie
    .split("; ")
    .find((entry) => entry.startsWith("lapis_sid="));
  if (!cookie) return null;
  try {
    const session = JSON.parse(
      decodeURIComponent(cookie.split("=").slice(1).join("="))
    );
    return session.sid ?? null;
  } catch {
    return null;
  }
}

Store the value on the business record that will eventually convert:

  • Signup: include it in the signup mutation, writing to accounts.lapis_click_id (a nullable column).
  • Checkout: put it in the checkout session metadata (for example, Stripe metadata.lapis_click_id) and read it back in the payment webhook.
  • Cross-domain (marketing site to app): pass it through the URL. See the decorator script in the Framer guide.

null is a valid value, because most traffic is organic. Never fabricate an ID.

3. Send the conversion from your backend

export async function sendLapisConversionEvent(event: {
  event_id: string;
  event_type: string;
  lapis_click_id: string | null;
  value?: number;
  currency?: string;
  external_user_id?: string;
  properties?: Record<string, unknown>;
}) {
  if (!event.lapis_click_id) return; // organic, so skip

  const response = await fetch(
    "https://api.trylapis.com/api/v1/pixel/server-events",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.LAPIS_SERVER_EVENT_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(event),
    }
  );
  return response.json();
}

Call it the moment the conversion is confirmed: the payment webhook, the post-verification signup handler, or the CRM sync. For the field reference and response codes, see the Conversion API. For which events to send, see the event guides.

Stack notes

  • Server components / SSR: the cookie is first-party and readable on the server too (cookies().get("lapis_sid") in Next.js). Parsing it on the server during signup avoids a client round-trip.
  • SPAs: the pixel records the landing page view automatically. Client-side route changes need no manual tracking for attribution to work, because the session already exists.
  • Browser conversions (optional, for example instant-feedback events): if (window.lapis) lapis("lead", { value: 50 }). See Lapis Pixel.
  • CSP: allow https://cdn.trylapis.com (script-src) and https://api.trylapis.com (connect-src).

Was this page helpful?