Stripe Metadata
If you create Stripe Checkout Sessions via the API, you can pass the click_id in the Stripe metadata field using the key aff_click_id. Selgeo reads this metadata from Stripe webhooks and attributes the conversion to the referring partner.
API Version: v1
When to use this approach
Use Stripe metadata when:
- You want to pass the
click_idalongside your ownclient_reference_idvalue (which Checkout also supports, but metadata gives you more flexibility). - You create Checkout Sessions but want to use
client_reference_idfor your own purposes.
Selgeo processes metadata on checkout.session.completed and invoice.paid webhook events. If you create Payment Intents directly, their metadata is not available to Selgeo. If you create Subscriptions directly (without Checkout), Stripe stores the metadata at invoice.subscription_details.metadata, not invoice.metadata, so Selgeo cannot read the aff_click_id. Use the Conversion API instead, or create subscriptions via Stripe Checkout (which supports mode: 'subscription').
For Stripe Checkout Sessions, you can use either client_reference_id (simpler, covered in Stripe Checkout) or metadata.aff_click_id (covered here). Both work. If you already use client_reference_id for your own internal tracking, use metadata instead.
The flow
┌────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Browser │ │ Your Server │ │ Selgeo API │
│ │ │ │ │ │
│ 1. Snippet │ │ │ │ │
│ stores │ │ │ │ │
│ click_id │ │ │ │ │
│ │ │ │ │ │
│ 2. Frontend │─────>│ 3. Create Stripe │ │ │
│ sends │ │ object with │ │ │
│ clickId to │ │ metadata: │ │ │
│ backend │ │ aff_click_id │ │ │
│ │ │ │ │ │
│ │ │ │ │ 4. Stripe webhook │
│ │ │ │ │ fires → │
│ │ │ │ │ Selgeo reads │
│ │ │ │ │ aff_click_id │
│ │ │ │ │ from metadata │
│ │ │ │ │ → attributes │
│ │ │ │ │ conversion │
└────────────────┘ └──────────────────┘ └──────────────────┘
The metadata key
Selgeo looks for the click_id in Stripe metadata under the key:
aff_click_id
This key is checked on Checkout Session objects (via checkout.session.completed) and on Invoice objects (via invoice.paid).
Implementation
Step 1: Read the click ID on the frontend
const clickId = __selgeo.getClickId();
Step 2: Send it to your backend
Include the clickId in your API request body, form data, or however you pass data to your server:
const clickId = __selgeo.getClickId();
const response = await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
planId: 'plan_pro',
clickId: clickId,
}),
});
Step 3: Pass it in Stripe metadata
Payment Intents
Direct Payment Intents are not supported for Stripe metadata attribution. Selgeo only processes checkout.session.completed and invoice.paid webhook events. For custom payment flows that use Payment Intents directly, use the Conversion API to report conversions server-side.
Direct Subscriptions
Setting metadata.aff_click_id on stripe.subscriptions.create() does not work. Stripe does not copy subscription metadata to invoice.metadata -- it stores it at invoice.subscription_details.metadata, which Selgeo does not currently read. For subscription billing, use Stripe Checkout with mode: 'subscription' (shown below), or report conversions via the Conversion API.
Checkout Sessions (metadata approach)
If you prefer to use metadata instead of client_reference_id on Checkout Sessions:
- JavaScript (Node.js)
- Python
- PHP
const stripe = require('stripe')('sk_test_YOUR_STRIPE_KEY');
app.post('/api/create-checkout', async (req, res) => {
const { priceId, clickId } = req.body;
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: priceId, quantity: 1 }],
success_url: 'https://your-site.com/success',
cancel_url: 'https://your-site.com/cancel',
// Use client_reference_id for your own tracking
client_reference_id: 'my_internal_ref_123',
// Use metadata for Selgeo attribution
metadata: {
aff_click_id: clickId || '',
},
});
res.json({ url: session.url });
});
import stripe
stripe.api_key = "sk_test_YOUR_STRIPE_KEY"
@app.route("/api/create-checkout", methods=["POST"])
def create_checkout():
data = request.get_json()
session = stripe.checkout.Session.create(
mode="subscription",
line_items=[{"price": data["priceId"], "quantity": 1}],
success_url="https://your-site.com/success",
cancel_url="https://your-site.com/cancel",
# Use client_reference_id for your own tracking
client_reference_id="my_internal_ref_123",
# Use metadata for Selgeo attribution
metadata={
"aff_click_id": data.get("clickId", ""),
},
)
return jsonify({"url": session.url})
$stripe = new \Stripe\StripeClient('sk_test_YOUR_STRIPE_KEY');
$data = json_decode(file_get_contents('php://input'), true);
$session = $stripe->checkout->sessions->create([
'mode' => 'subscription',
'line_items' => [['price' => $data['priceId'], 'quantity' => 1]],
'success_url' => 'https://your-site.com/success',
'cancel_url' => 'https://your-site.com/cancel',
// Use client_reference_id for your own tracking
'client_reference_id' => 'my_internal_ref_123',
// Use metadata for Selgeo attribution
'metadata' => [
'aff_click_id' => $data['clickId'] ?? '',
],
]);
echo json_encode(['url' => $session->url]);
How Selgeo processes metadata
For checkout.session.completed events, Selgeo checks for the click_id in this order:
client_reference_idon the Checkout Session (if present and is a valid UUID).metadata.aff_click_idon the Checkout Session.
If both are present, client_reference_id takes priority. This means you can safely set both without conflict -- Selgeo will use whichever it finds first.
For invoice.paid events (initial invoices without a preceding Checkout Session), Selgeo checks metadata.aff_click_id and metadata.aff_promo_code directly on the invoice object.
Recurring subscriptions
For subscriptions created via Checkout (mode: 'subscription'), you only need to pass aff_click_id when creating the Checkout Session. Selgeo attributes the initial conversion from the checkout.session.completed event and automatically attributes subsequent invoice payments (renewals) to the same partner by matching the Stripe Customer ID. You do not need to pass the click_id again on renewals.
Empty values
If the visitor was not referred by a partner (clickId is null), you can either:
- Omit the key entirely -- do not include
aff_click_idin metadata. - Pass an empty string --
aff_click_id: ''. Selgeo ignores empty strings.
Both approaches are safe. The Stripe object is created normally, and Selgeo treats it as a non-referred conversion.
Testing
- Create a tracking link and a test partner in the Selgeo dashboard.
- Visit your site via the tracking link to register a click.
- Trigger your payment flow -- the
click_idshould be passed to your backend and included in Stripe metadata asaff_click_id. - Complete the payment using Stripe test card
4242 4242 4242 4242. - Check the Selgeo dashboard -- verify the conversion appears and is attributed to the test partner.
Verifying metadata in Stripe
You can confirm the metadata was set correctly in the Stripe Dashboard:
- Find the Checkout Session.
- Scroll down to the Metadata section.
- Verify
aff_click_idcontains the expected UUID.
Comparison of attribution methods
| Method | Stripe object | Key | Best for |
|---|---|---|---|
client_reference_id | Checkout Session only | client_reference_id | Simple Checkout integrations |
metadata.aff_click_id | Checkout Session | metadata.aff_click_id | When client_reference_id is already used for other purposes |
| Payment Link auto-rewrite | Payment Link URL | client_reference_id (query param) | Zero-backend Stripe Payment Links |
Next steps
- Conversion API -- for non-Stripe conversions
- Webhooks -- receive notifications when conversions are attributed
- Test Mode -- detailed testing guide