In this article, I show you how to report a conversion event (a Purchase) to the Facebook ConversionsAPI generated by a user who clicked on my FB ad and purchased on my site using Stripe’s Buy button. The conversion data includes the value of the event as well as Personal Identifiers that will help the FB targeting algorithm improve its precision and understand my audiences better. The event also contains attribution data, such as the fbc and fbp, so that I’m telling FB CAPI which exact ad generated the sale. This training data will improve my ROAS.
How it works:
I embedded the Stripe’s Buy button code on my landing page, which also has the Meta pixel installed and a Google Tag Manager container. As the user lands on the page coming from a FB Ad, the GTM container records the fbp and fbc values as GTM variables, then the user clicks on the Buy button (find button below) and goes to the Stripe checkout flow, when he completes the purchase Stripe takes him to my predefined thank you page
Stripe automatically appends the session Id to the thank you page, so when the user lands on the thank you page, a GTM script triggered by a PageView (see code below) will send the session id, as well as the fbc, fbp, fbclid, user agent and an eventId to a serverless function at vercel.com.
<script>
var url = new URL(window.location.href);
var sessionId = url.searchParams.get('session_id');
if (sessionId) {
var fbcid = '{{fbcid}}';
var fbc = '{{fbc}}';
var fbp = '{{fbp}}';
var userAgent = navigator.userAgent;
var sourceUrl = window.location.href;
// Generate a unique event_id
var eventId = 'evt_' + Date.now() + '_' + Math.floor(Math.random() * 1000000);
console.log("Stripe Session ID found:", sessionId);
console.log("Facebook Tracking Data Captured by GTM:");
console.log('fbcid: ' + (fbcid || 'Not Found'));
console.log('fbc: ' + (fbc || 'Not Found'));
console.log('fbp: ' + (fbp || 'Not Found'));
console.log("User Agent:", userAgent);
console.log("Source URL:", sourceUrl);
console.log("Event ID:", eventId);
var endpoint = 'https://123-five-gamma.vercel.app/api/stripebuytofb';
fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: sessionId,
source: 'gtm_tag',
fbc: fbc,
fbp: fbp,
userAgent: userAgent,
fbclid: fbcid,
sourceUrl: sourceUrl,
event_id: eventId // Include event_id
})
})
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log('Vercel function response:', data);
})
.catch(function(error) {
console.error('An error occurred while sending data to Vercel:', error);
});
} else {
console.warn('Stripe Session ID not found in the URL. CAPI event not sent.');
}
</script>
The vercel function (see code below) will use the session Id to ask Stripe’s API the deatils of the Purchase, then it will take all the parameters and format them according to FB CAPI’s requirements (including hashing the personal info), it uses my Meta pixeId and token to authenticate and post the Purchase event,
// Import necessary libraries.
// You will need to install 'crypto' and 'stripe' if they're not already installed.
// npm install crypto stripe
import Stripe from 'stripe';
import crypto from 'crypto';
// Replace with your actual values from Vercel's environment variables.
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
const FACEBOOK_ACCESS_TOKEN = process.env.FACEBOOK_ACCESS_TOKEN;
const FACEBOOK_PIXEL_ID = process.env.FACEBOOK_PIXEL_ID;
// Create a new Stripe instance.
const stripe = new Stripe(STRIPE_SECRET_KEY);
// Function to hash the PII data.
// It's crucial to hash PII before sending it to Facebook.
function hash(data) {
if (!data) return null;
return crypto.createHash('sha256').update(data.trim().toLowerCase()).digest('hex');
}
// Define the OPTIONS method for CORS preflight requests.
// This is necessary to allow the front-end to make POST requests from a different domain.
export async function OPTIONS() {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}
// Main POST function to handle the purchase event.
export async function POST(request) {
let body;
try {
body = await request.json();
console.log("Received data from front-end:", body);
} catch (error) {
return new Response(JSON.stringify({ error: "Invalid JSON in request body." }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
const {
sessionId,
fbclid,
fbc,
fbp,
clientUserAgent,
sourceUrl
} = body;
// Check for essential data from the front-end.
if (!sessionId) {
return new Response(JSON.stringify({ error: "No sessionId provided." }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
try {
// --- STEP 1: RETRIEVE PURCHASE DATA FROM STRIPE ---
// Retrieve the full Stripe session, including the payment details and line items.
const session = await stripe.checkout.sessions.retrieve(sessionId, {
expand: ['payment_intent', 'line_items'],
});
// Ensure the payment was successful.
if (session.payment_intent.status !== 'succeeded') {
return new Response(JSON.stringify({ error: 'Payment not succeeded' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
console.log("Stripe session retrieved successfully.");
// Extract and process customer and purchase data.
const customerDetails = session.customer_details;
const purchaseAmount = session.amount_total;
const purchaseCurrency = session.currency;
const lineItems = session.line_items.data;
// --- STEP 2: CONSTRUCT THE FACEBOOK CAPI PAYLOAD ---
const facebookEventData = {
data: [{
event_name: 'Purchase',
event_time: Math.floor(Date.now() / 1000),
event_source_url: sourceUrl,
action_source: 'website',
user_data: {
// Hash PII for privacy and compliance
em: hash(customerDetails?.email),
fn: hash(customerDetails?.name),
ph: hash(customerDetails?.phone),
// Use client data for better attribution and deduplication
client_ip_address: request.headers['x-forwarded-for'] || request.headers['x-real-ip'] || request.ip,
client_user_agent: clientUserAgent,
// Use cookies for deduplication
fbc: fbc,
fbp: fbp,
},
custom_data: {
currency: purchaseCurrency.toUpperCase(),
value: (purchaseAmount / 100).toFixed(2), // Convert from cents to dollars
// Process line items into a format Facebook can use
contents: lineItems.map(item => ({
id: item.price.product, // Assuming the product ID is what you need
quantity: item.quantity,
item_price: (item.price.unit_amount / 100).toFixed(2)
})),
content_type: 'product',
content_ids: lineItems.map(item => item.price.product),
num_items: lineItems.reduce((total, item) => total + item.quantity, 0),
},
}],
// Use this optional parameter to ensure your events are deduplicated correctly
test_event_code: null // Use 'TESTxxxx' from your Events Manager for testing
};
// --- STEP 3: SEND DATA TO FACEBOOK CAPI ---
const fbEndpoint = `https://graph.facebook.com/v20.0/${FACEBOOK_PIXEL_ID}/events?access_token=${FACEBOOK_ACCESS_TOKEN}`;
const fbResponse = await fetch(fbEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(facebookEventData),
});
const fbResponseData = await fbResponse.json();
if (fbResponse.ok) {
console.log("Purchase event sent to Facebook successfully:", fbResponseData);
} else {
console.error("Failed to send Purchase event to Facebook:", fbResponseData);
}
// --- STEP 4: RESPOND TO FRONT-END ---
return new Response(JSON.stringify({
message: 'Purchase event processed and sent to Facebook',
stripeSession: session,
facebookResponse: fbResponseData
}), {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
'Content-Type': 'application/json'
},
});
} catch (error) {
console.error("Error in CAPI function:", error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
"Access-Control-Allow-Origin": "*",
'Content-Type': 'application/json'
},
});
}
}
I get 3 answers, one confirms having sent the data, the second confirms the stripe session access and the 3rd confirms successful post to FB CAPI:

a few instants later I can see the event arriving server-side on the FB Events Manager

Since it contains the fbc, the Facebook Ads manager will know to which ad the Purchase should be attributed to!
Available for hire. If you want me to implement this automation for your business (or if you want a guided demo)
Other Demos
- Example of sending a Lead (from html form) to FB CAPI
- Example of OutboundClick to FB ConversionsAPI
- Example of sending a Stripe Purchase to FB CAPI