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
- Your client-side code reads
engagetrack_visitor_idandengagetrack_session_idfrom the SDK and sends them to your server - Your server creates a PaymentIntent with these values in the
metadatafield - After payment, Stripe sends a
payment_intent.succeededwebhook to EngageTrack - 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 paymentStep 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:
- Go to Stripe Dashboard → Developers → Webhooks
- Click Add endpoint
- Enter the webhook URL:
https://api.engagetrack.net/api/v1/webhooks/revenue/stripe/{YOUR_SITE_PUBLIC_ID}
- Under Events to send, select:
payment_intent.succeededcharge.refunded
- 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
- Open your site and navigate to the custom checkout page
- Open DevTools → Console and run
window.engagetrack.getVisitorId()to confirm the SDK is loaded and returning an ID - Complete a test payment using card
4242 4242 4242 4242 - Check your EngageTrack dashboard → Revenue tab
- 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.succeededwhen adding the webhook endpoint