Ambassly

Published · Last updated

Pass Ambassly Referrals into Stripe Checkout

Ambassly attributes paid customers when your checkout code sends the referral into Stripe Checkout. Use client_reference_id for the affiliate code, and set these Checkout Session metadata keys so ingest can resolve code, per-content link, and cookie timestamp:

  • metadata.ambassly — affiliate code
  • metadata.ambassly_link — opaque per-content link token (from ?via_link= / getReferral().link)
  • metadata.ambassly_ts — click timestamp as a string (from getReferral().ts, epoch ms)

Ambassly accepts the code from either client_reference_id or metadata.ambassly, but sending both plus link and ts makes attribution inspectable in Stripe and durable after the browser leaves your site.

Node and Express

Install dependencies:

npm install express stripe dotenv

Create server.js:

require("dotenv").config();

const express = require("express");
const Stripe = require("stripe");

const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
const app = express();

app.use(express.json());

app.get("/", (_req, res) => {
  res.type("html").send(`<!doctype html>
<html>
  <head>
    <title>Checkout</title>
    <script
      src="https://ambassly.com/a.js"
      data-ambassly="COMPANY_PUBLIC_ID"
      async
    ></script>
  </head>
  <body>
    <button id="checkout">Checkout</button>

    <script>
      document.getElementById("checkout").addEventListener("click", async () => {
        const referral = window.Ambassly?.getReferral?.();

        const response = await fetch("/create-checkout-session", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            ambassly: referral?.code ?? null,
            ambassly_link: referral?.link ?? null,
            ambassly_ts: referral?.ts != null ? String(referral.ts) : null
          })
        });

        const { url } = await response.json();
        window.location.href = url;
      });
    </script>
  </body>
</html>`);
});

app.post("/create-checkout-session", async (req, res) => {
  const ambassly =
    typeof req.body.ambassly === "string" && req.body.ambassly.length > 0
      ? req.body.ambassly
      : null;
  const ambassly_link =
    typeof req.body.ambassly_link === "string" && req.body.ambassly_link.length > 0
      ? req.body.ambassly_link
      : null;
  const ambassly_ts =
    typeof req.body.ambassly_ts === "string" && req.body.ambassly_ts.length > 0
      ? req.body.ambassly_ts
      : null;

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [
      {
        price: process.env.STRIPE_PRICE_ID,
        quantity: 1
      }
    ],
    success_url: `${process.env.APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.APP_URL}/`,
    ...(ambassly
      ? {
          client_reference_id: ambassly,
          metadata: {
            ambassly,
            ...(ambassly_link ? { ambassly_link } : {}),
            ...(ambassly_ts ? { ambassly_ts } : {})
          }
        }
      : {})
  });

  res.json({ url: session.url });
});

app.listen(3000, () => {
  console.log("Listening on http://localhost:3000");
});

Create .env:

STRIPE_SECRET_KEY=sk_test_...
STRIPE_PRICE_ID=price_...
APP_URL=http://localhost:3000

Run it:

node server.js

Then visit:

http://localhost:3000/?via=TESTCODE&via_link=youtube-auth

Next.js App Router

Install Stripe:

npm install stripe

Add the script to your app shell, replacing COMPANY_PUBLIC_ID.

// 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://ambassly.com/a.js"
          data-ambassly="COMPANY_PUBLIC_ID"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

Create a checkout button:

// app/checkout-button.tsx
"use client";

type AmbasslyReferral = {
  code: string;
  link?: string | null;
  ts?: number;
};

declare global {
  interface Window {
    Ambassly?: {
      getReferral?: () => AmbasslyReferral | null;
      getCode?: () => string | null;
      getLink?: () => string | null;
    };
  }
}

export function CheckoutButton() {
  async function startCheckout() {
    const referral = window.Ambassly?.getReferral?.();

    const response = await fetch("/api/checkout", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        ambassly: referral?.code ?? null,
        ambassly_link: referral?.link ?? null,
        ambassly_ts: referral?.ts != null ? String(referral.ts) : null
      })
    });

    if (!response.ok) {
      throw new Error("Checkout failed");
    }

    const { url } = await response.json();
    window.location.href = url;
  }

  return <button onClick={startCheckout}>Checkout</button>;
}

Create the route handler:

// app/api/checkout/route.ts
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const body = await request.json();
  const ambassly =
    typeof body.ambassly === "string" && body.ambassly.length > 0
      ? body.ambassly
      : null;
  const ambassly_link =
    typeof body.ambassly_link === "string" && body.ambassly_link.length > 0
      ? body.ambassly_link
      : null;
  const ambassly_ts =
    typeof body.ambassly_ts === "string" && body.ambassly_ts.length > 0
      ? body.ambassly_ts
      : null;

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [
      {
        price: process.env.STRIPE_PRICE_ID!,
        quantity: 1
      }
    ],
    success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/pricing`,
    ...(ambassly
      ? {
          client_reference_id: ambassly,
          metadata: {
            ambassly,
            ...(ambassly_link ? { ambassly_link } : {}),
            ...(ambassly_ts ? { ambassly_ts } : {})
          }
        }
      : {})
  });

  return Response.json({ url: session.url });
}

Use the button on a page:

// app/pricing/page.tsx
import { CheckoutButton } from "../checkout-button";

export default function PricingPage() {
  return <CheckoutButton />;
}

Set environment variables:

STRIPE_SECRET_KEY=sk_test_...
STRIPE_PRICE_ID=price_...
NEXT_PUBLIC_SITE_URL=http://localhost:3000

Test with:

http://localhost:3000/pricing?via=TESTCODE&via_link=youtube-auth

Payment Links

Stripe Payment Links support client_reference_id as a URL parameter. Use that for Ambassly attribution when you are not creating Checkout Sessions from your server.

Create your normal Payment Link in Stripe, then append the current Ambassly code (and optional link token as CODE:link) before redirecting the customer.

<script
  src="https://ambassly.com/a.js"
  data-ambassly="COMPANY_PUBLIC_ID"
  async
></script>

<button id="payment-link">Checkout</button>

<script>
  const paymentLink = "https://buy.stripe.com/test_abc123";

  document.getElementById("payment-link").addEventListener("click", () => {
    const referral = window.Ambassly?.getReferral?.();
    const url = new URL(paymentLink);

    if (referral?.code) {
      // Ambassly accepts "CODE" or "CODE:linkToken" in client_reference_id.
      const ref =
        referral.link != null && referral.link !== ""
          ? `${referral.code}:${referral.link}`
          : referral.code;
      url.searchParams.set("client_reference_id", ref);
    }

    window.location.href = url.toString();
  });
</script>

Do not put secrets, email addresses, or private customer data in client_reference_id. Use only the Ambassly referral code and optional link token.

Payment Links do not give your page a server-side Checkout Session creation step where you can set dynamic metadata.ambassly / metadata.ambassly_link / metadata.ambassly_ts per click. If you need full metadata, use server-created Checkout Sessions.

What Ambassly reads

When Stripe sends checkout.session.completed, Ambassly resolves:

  1. Affiliate code from client_reference_id (first segment before :) or metadata.ambassly
  2. Per-content link token from metadata.ambassly_link (or metadata.via_link), else the segment after : in client_reference_id
  3. Cookie/click timestamp from metadata.ambassly_ts (epoch ms or seconds as a string)

Those metadata keys must match exactly — ambassly, ambassly_link, and ambassly_ts.