Stripe PaymentIntent

Set up revenue attribution with Stripe PaymentIntent for custom checkout flows.

Stripe PaymentIntent

If you are building a custom checkout form with Stripe Elements, you create PaymentIntents on the server. This guide shows how to attach EngageTrack metadata to the PaymentIntent so revenue is attributed correctly.

How It Works

  1. Your client-side code reads engagetrack_visitor_id and engagetrack_session_id from the SDK and sends them to your server
  2. Your server creates a PaymentIntent with these values in the metadata field
  3. After payment, Stripe sends a payment_intent.succeeded webhook to EngageTrack
  4. EngageTrack reads the metadata and attributes the revenue

Step 1: Send IDs from the Client

When your checkout form loads, read the EngageTrack IDs and include them in the request to your server:

const visitorId = window.engagetrack?.getVisitorId() || "";
const sessionId = window.engagetrack?.getSessionId() || "";
 
const response = await fetch("/api/create-payment-intent", {
	method: "POST",
	headers: { "Content-Type": "application/json" },
	body: JSON.stringify({
		amount: 2999, // $29.99 in cents
		engagetrack_visitor_id: visitorId,
		engagetrack_session_id: sessionId,
	}),
});
 
const { clientSecret } = await response.json();
 
// Use clientSecret with Stripe Elements to confirm payment

Step 2: Create the PaymentIntent with Metadata

Node.js / Express

import Stripe from "stripe";
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
 
app.post("/api/create-payment-intent", async (req, res) => {
	const { amount, engagetrack_visitor_id, engagetrack_session_id } = req.body;
 
	const paymentIntent = await stripe.paymentIntents.create({
		amount,
		currency: "usd",
		metadata: {
			engagetrack_visitor_id: engagetrack_visitor_id || "",
			engagetrack_session_id: engagetrack_session_id || "",
		},
	});
 
	res.json({ clientSecret: paymentIntent.client_secret });
});

Next.js App Router (Route Handler)

import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function POST(request: NextRequest) {
	const { amount, engagetrack_visitor_id, engagetrack_session_id } =
		await request.json();
 
	const paymentIntent = await stripe.paymentIntents.create({
		amount,
		currency: "usd",
		metadata: {
			engagetrack_visitor_id: engagetrack_visitor_id ?? "",
			engagetrack_session_id: engagetrack_session_id ?? "",
		},
	});
 
	return NextResponse.json({ clientSecret: paymentIntent.client_secret });
}

The SDK stores IDs in browser storage (not cookies), so the client-to-server approach shown above is the recommended pattern. Read the IDs with window.engagetrack.getVisitorId() and window.engagetrack.getSessionId(), then send them to your server in the request body.

Step 3: Set Up the Webhook

Add the EngageTrack webhook endpoint in your Stripe dashboard:

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Click Add endpoint
  3. Enter the webhook URL:
https://api.engagetrack.net/api/v1/webhooks/revenue/stripe/{YOUR_SITE_PUBLIC_ID}
  1. Under Events to send, select:
    • payment_intent.succeeded
    • charge.refunded
  2. Click Add endpoint

Stripe Elements Example

Here is a complete client-side example using Stripe Elements with React:

import { useState, useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";
import {
	Elements,
	PaymentElement,
	useStripe,
	useElements,
} from "@stripe/react-stripe-js";
 
const stripePromise = loadStripe(
	process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
);
 
function CheckoutForm() {
	const stripe = useStripe();
	const elements = useElements();
	const [loading, setLoading] = useState(false);
 
	async function handleSubmit(e: React.FormEvent) {
		e.preventDefault();
		if (!stripe || !elements) return;
 
		setLoading(true);
		const { error } = await stripe.confirmPayment({
			elements,
			confirmParams: {
				return_url: "https://yoursite.com/success",
			},
		});
 
		if (error) {
			console.error(error.message);
		}
		setLoading(false);
	}
 
	return (
		<form onSubmit={handleSubmit}>
			<PaymentElement />
			<button disabled={!stripe || loading}>
				{loading ? "Processing..." : "Pay $29.99"}
			</button>
		</form>
	);
}
 
export default function CheckoutPage() {
	const [clientSecret, setClientSecret] = useState("");
 
	useEffect(() => {
		const visitorId = window.engagetrack?.getVisitorId() || "";
		const sessionId = window.engagetrack?.getSessionId() || "";
 
		fetch("/api/create-payment-intent", {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({
				amount: 2999,
				engagetrack_visitor_id: visitorId,
				engagetrack_session_id: sessionId,
			}),
		})
			.then((res) => res.json())
			.then(({ clientSecret }) => setClientSecret(clientSecret));
	}, []);
 
	if (!clientSecret) return <div>Loading checkout...</div>;
 
	return (
		<Elements stripe={stripePromise} options={{ clientSecret }}>
			<CheckoutForm />
		</Elements>
	);
}

Verify

  1. Open your site and navigate to the custom checkout page
  2. Open DevTools → Console and run window.engagetrack.getVisitorId() to confirm the SDK is loaded and returning an ID
  3. Complete a test payment using card 4242 4242 4242 4242
  4. Check your EngageTrack dashboard → Revenue tab
  5. Confirm the payment appears and is attributed to your traffic source

If the payment is not attributed:

  • Verify that the PaymentIntent metadata contains the correct keys (check in Stripe Dashboard → Payments → select payment → Metadata section)
  • Ensure the webhook endpoint is receiving events (Stripe Dashboard → Webhooks → recent deliveries)
  • Confirm you selected payment_intent.succeeded when adding the webhook endpoint