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 codemetadata.ambassly_link— opaque per-content link token (from?via_link=/getReferral().link)metadata.ambassly_ts— click timestamp as a string (fromgetReferral().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:
- Affiliate code from
client_reference_id(first segment before:) ormetadata.ambassly - Per-content link token from
metadata.ambassly_link(ormetadata.via_link), else the segment after:inclient_reference_id - 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.