BlogCompose

Paywall copy that rewrites itself for every user

Your paywall says "Upgrade to Pro for unlimited generations." A teacher reads it and shrugs. A student on a budget reads it and closes the tab. The same words, two lost conversions - because the words were written for nobody in particular.

One paywall, many readers

The conversion moment is the least personalized screen in most products. A user hits a limit, sees a generic "Upgrade to Pro," and decides in about three seconds. That decision depends entirely on whether the copy connects what they were just doing to what they would get. A static headline cannot do that, because the same string ships to a teacher building quizzes for thirty students, a student watching every dollar, and a founder evaluating the tool for a team. Each of them needs a different reason to pay, and you are giving all three the same sentence.

The objection is different for each persona

Conversion copy works when it answers the specific objection in the reader’s head. Those objections are not the same. A teacher cares about saving prep time across many students. A student cares about price and whether the free tier is enough. A founder cares about seats, limits, and whether it scales to a team. Write one headline and you answer one of those objections and miss the other two. The information to know which reader you are talking to is already in your analytics: their role, their usage shape, what they just hit a wall on.

  • user_usage - what they consumed and which limit they just hit
  • user_attributes - role, team size, declared goal
  • user_events - the feature they were mid-flow on when blocked
  • conversion_signals - what tends to precede an upgrade for users like them

Generate the pitch at the moment of friction

A compose type for the paywall reads the user’s usage and profile and returns structured copy: a headline, two or three value props, and a CTA label. You define the intent once - "Write a short, honest upgrade pitch for this user based on what they were doing and the limit they hit; speak to their role" - and an output schema. When a user hits a cap, your gate calls compose and renders the result instead of a hardcoded banner. The copy is grounded in their real numbers, so it can say "You have generated 47 quizzes this month" instead of "unlimited generations."

// Called from your limit gate, server-side
import { createClient, VeveeError } from '@vevee/sdk';

const vevee = createClient({ apiKey: process.env.VEVEE_SECRET_KEY! });

async function paywallCopy(userId: string) {
  try {
    const { output } = await vevee.compose<{
      headline: string;
      valueProps: string[];
      ctaLabel: string;
    }>('paywall-pitch', userId);
    return output;
  } catch (e) {
    if (e instanceof VeveeError) return STATIC_PAYWALL; // never hide the upgrade button
    throw e;
  }
}

The same wall, three pitches

One compose type, resolved per user, produces a pitch aimed at the person reading it. The teacher gets a time-saving frame, the student gets a price-and-value frame, the founder gets a scale-and-seats frame - from the same call, with no persona branching in your code. You tune the angles by editing the prompt in the dashboard and watching conversion move, not by shipping a new paywall component for every segment.

  • Teacher: "You’ve built 47 quizzes this month. Pro removes the cap so grading week never stops you."
  • Student: "You’re using Vevee like a pro. Pro is the price of one coffee a month and unlocks the rest of the semester."
  • Founder: "Your team is hitting the team limit. Pro adds seats and a shared usage dashboard for everyone."

Honesty beats hype, and the data enforces it

Because the copy is generated from real usage, it stays truthful: it references numbers the user can verify in their own dashboard, which is more persuasive than a superlative. Constrain the model with a tight output schema and a low maxOutputTokens so it returns a headline and a couple of props, not a wall of marketing text. Keep the prompt instruction explicit - speak to their role, cite their actual usage, no false scarcity. A personalized pitch that is also accurate converts better and ages better than a generic one that overpromises.

Measure it like any other conversion change

Generate the pitch when the user hits the limit, cache it for the life of that paywall view, and capture a paywall_shown analytics event alongside it so the personalized copy sits in the same funnel as everything else. Then compare upgrade rate for the generated paywall against your old static one. Compose returns usage.costMicroUsd per call, so you can put a real number on cost-per-incremental-conversion and decide, with data, whether personalized paywall copy pays for itself. For most AI products that charge a monthly subscription, one extra upgrade covers thousands of generations.

See your own numbers in two minutes.Add the SDK and your users, costs and revenue fill in on the next request.
Join waitlist

Keep reading

ShareCopy a link to this post.

Set your first limit in about five minutes.

50k operations free every month. No card required.
Join waitlist