The setup is familiar. You have a Stripe Payment Link. You paste it into posts, into your bio, into your newsletter. Money arrives. And when you open the Stripe dashboard, every payment looks identical: a name, an email, an amount, a timestamp. Nothing tells you which of the forty things you published that month actually produced the sale.
Most people assume closing that gap requires a proper backend: a database of sessions, a custom checkout, webhook handlers, an engineer. It does not. Stripe ships one field that does the entire job, and if you can create a Payment Link you can use it.
The one field that does the work: client_reference_id
Every Stripe Payment Link accepts a query parameter called client_reference_id. Append it to the link and whatever value you put there travels with the customer through checkout and comes back out attached to the completed session. A link ending in ?client_reference_id=post_8842 produces a payment stamped post_8842.
You can see the value without writing a line of code. It appears on the Checkout Session in the Stripe dashboard, it is available in the API, and it is included in the checkout.session.completed webhook payload. Stripe restricts it to alphanumeric characters, dashes and underscores, and caps the length, so keep the value short and machine-shaped. Do not put an email address or a name in there.
That constraint is the whole design. The field is meant to hold a reference to a record in your own system, not the data itself. You generate an id, you remember what it means, and Stripe hands it back to you when the money lands.
The chain from post to payment
Attribution is not one clever trick. It is four boring steps done reliably, and the failure of any one of them breaks the chain.
- Give each post its own link. Not one link used everywhere — one per post, ideally one per post per platform.
- Record the click. When someone follows the link, note which post it belonged to and set a first-party cookie so you can recognise them when they come back later.
- Carry the id into checkout. Append it to the Payment Link URL as client_reference_id before the customer clicks through to Stripe.
- Match the payment. When checkout.session.completed fires, read the id back and join the sale to the post that started it.
Step two is where the no-backend version gets interesting. If your visitor clicks straight through from post to Payment Link, you can do the whole thing with a redirect: the tracked link knows which post it belongs to, so it can append client_reference_id to the Stripe URL and forward the visitor on. No storage, no session, no server. The id rides in the URL from start to finish.
When the visitor does not buy immediately
Most people do not buy on the first visit. They click your post, look at your site, close the tab, and come back four days later from a bookmark or a Google search. If the id only lives in the URL, that second visit arrives with nothing attached and the sale becomes unattributable.
The fix is a first-party cookie set on the first visit. When the tracked link sends the visitor to your site, drop a cookie holding the tracking id. When they later click through to your Payment Link, read the cookie and append its value as client_reference_id. First-party cookies set by your own domain are not blocked the way third-party tracking cookies are, so this survives normal browser privacy defaults.
You still need a rule for what happens when there is no cookie and no id. The right answer is to attribute nothing. A sale you cannot trace is a sale you should not credit to anything, because credited-to-the-wrong-post is worse than uncredited. An honest gap tells you your tracking has holes; a fabricated match tells you to double down on a post that did nothing.
Payment Links versus a custom Checkout Session
If you already build Checkout Sessions in code, you have more options: you can set client_reference_id server-side, attach arbitrary metadata to the session, and pass values through to the resulting subscription or invoice. Metadata is more flexible than client_reference_id because it holds key-value pairs and is not restricted to a single token.
Payment Links are the no-code path, and for attribution purposes they are almost as good. You lose the ability to attach structured metadata at creation time, but you keep the one field that matters. If your entire product is a Payment Link in a bio and a Notion page for onboarding, that field is the difference between knowing and guessing.
One practical note for subscriptions: client_reference_id lives on the Checkout Session, not on the subscription that the session creates. If you want the attribution to persist across future renewal invoices, copy the value onto the subscription or customer as metadata when the first session completes. Otherwise you will attribute the first payment and lose every renewal after it.
Putting a value in the field that means something
Do not put raw text in there. Use a short opaque id that maps to a record you control, and keep the mapping somewhere you can query. A value like a1b2c3d4 that resolves to “LinkedIn, 14 August, the post about pricing mistakes” is far more useful than trying to cram all three facts into a length-limited alphanumeric field.
If you genuinely have no system to store the mapping, an encoded short form works: platform initial, date, and a two-character post code, for example li0814pm. It is ugly, it does not scale past a few hundred posts, and you will regret it eventually. But it beats no attribution, and it can be migrated later.
Where the manual version stops scaling
The mechanics above are genuinely simple. What is not simple is doing them for every post, on every platform, every day, forever. Publishing the same idea to X, LinkedIn, Threads, Bluesky and a newsletter means five distinct tracked links, five ids, five rows in the mapping, and five chances to paste the wrong one. Two weeks in, most people quietly go back to one link for everything.
That is the part seenpaid automates. It publishes your post to the networks you have connected, mints a distinct tracked link per post and platform automatically, carries the tracking id through to your Stripe checkout, and reads your Stripe account to match the resulting payments back. You never touch a client_reference_id by hand. The attribution logic is the same logic described above, run consistently instead of when you remember.
What good looks like once it works
The output of all this is one sentence you could not previously say: this specific post, on this specific platform, produced this many dollars. Not clicks. Not impressions. Dollars, matched to payments that actually cleared.
That sentence changes behaviour faster than any engagement chart. The post you were proud of made nothing. The throwaway reply you almost did not publish brought in three customers. The platform you spend the most time on turns out to be the one that pays least. None of that is visible until the click and the charge share an identifier, and Stripe gave you the field to do it with. Use it.