Atune Docs
SDK reference

createAtuneMiddleware()

Auto-capture behavior from every server request, fire-and-forget.

createAtuneMiddleware() returns a middleware that records attributed server requests as signals. npx atune init installs it for you; add it by hand when you want control or aren't on Next.js.

// proxy.ts on Next.js 16; middleware.ts on Next.js 14/15
import { createAtuneMiddleware } from "atune";

const atune = createAtuneMiddleware();

export default function capture(request: Request) {
  return atune(request);
}

What it captures

  • Each request is recorded as http.<method>.<normalized-route>, resolved to the account and user from your auth session.
  • Fire-and-forget. The call is never awaited on your request path — a slow or down Atune can never delay or break your app.
  • Bodies are never read. No passwords, tokens, or card numbers ever leave your app.

Chaining with an existing middleware

Already running Clerk (or any other) middleware? Call atune() inside your handler so both run. With Clerk, pass the account from the auth your callback receives (atune 0.1.5 or later):

import { clerkMiddleware } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import { clerkAccount, createAtuneMiddleware } from "atune";

const atune = createAtuneMiddleware();

export default clerkMiddleware(async (auth, request) => {
  await auth.protect(); // Keep your app's existing auth policy.
  return atune(request, NextResponse.next(), { account: clerkAccount(auth) });
});

Clerk's session can't be read from inside a proxy any other way, so without clerkAccount(auth) capture sends no events. For a custom response, return atune(request, response, { account: clerkAccount(auth) }) after your existing handler. It preserves that response while scheduling capture. Do not replace your auth policy with this example. npx atune init does not reliably chain all existing middleware; inspect its changes before running the app.

Account resolution

  • Clerk: pass { account: clerkAccount(auth) } as shown above.
  • Supabase: detected automatically from the request's session cookies.
  • NextAuth: Atune tries to read the session. If no events arrive, pass getAccount.

If your app has teams or organizations, the team/org is the account; otherwise the signed-in user. See the Next.js guide for details and the escape hatch when your auth can't be auto-detected. Supabase detection needs @supabase/ssr, NEXT_PUBLIC_SUPABASE_URL and either NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY or NEXT_PUBLIC_SUPABASE_ANON_KEY.

If no account is resolved, capture sends no event. For B2B apps, verify that members of one company share an account ID. Override resolution with getAccount using your verified server session when auto-detection does not match your customer model; never trust an unsigned client account header.

getAccount can also return a subscription (stage, plan, seats, mrr, currency, trialEndsAt). It is sent with each event, so Atune knows who pays and can see downgrades. See Trial, paying and subscription value.

On this page