Dub conversion tracking: lead and sale APIs with Stripe customer mapping
Go deeper on this topic
SaaS integrations
Give AI the context to continue the work: source conversations, open tasks, and unresolved details, from adoption decisions to integration code.
Article 2 of 5 in this series.
Next reads
Record Slack huddles and feed AI at 22:00: six Circleback settings
A Circleback workflow for giving AI usable work context: Slack huddles, a 22:00 sync, source statements, open tasks, and adoption criteria. Separates workflow experience, documented features, and local test evidence.
Circleback action items to tasks: testing webhook signatures, owners, and replay keys
Runnable Circleback webhook code tests signatures, owners, SQLite upserts, and stale replays. A separate status example preserves destination-owned DONE. Uses synthetic data.
Track Dub referral links in Japanese and English without breaking referral IDs
Adding UTMs can re-encode existing referral parameters even when their decoded values stay the same. Reproduce spaces, tildes, and duplicate keys locally, with limits for signed URLs and commission attribution.
Use the topic hub as a map, then continue to the next article in the series. New here? Start at the home hub →
More referral clicks do not by themselves tell you which visitors signed up or paid. Dub conversion tracking connects a click ID to your internal customer ID through a lead, then uses that customer ID for a sale. This guide continues from bilingual UTM construction to signup and revenue events.
We checked the official server guide, APIs, and Stripe integration on October 21, 2026. Our runnable experiment only builds request JSON from synthetic input. We have not tested Dub API writes, live attribution, Stripe connectivity, or confirmed commissions. Links go to the official Dub site; we do not use an unenrolled referral link.
Plan requirements and link settings
The official server-side guide requires Business or above. Enable Conversion Tracking for the target link. The workspace Tracking setting applies to future links; use Link Builder for a specific existing link. API-created or updated links can set trackConversion: true. Dub Partners links have conversion tracking enabled by default.
Install Dub Analytics next. The documented setup stores the destination URL's dub_id in a first-party cookie for your server to read at signup. UTMs classify traffic; dub_id identifies a click. Do not fabricate a click ID from a UTM or article slug. This workflow requires control of your application's signup and payment flows. Publishing someone else's referral link alone does not give you their purchase-event integration.
Link a signup to your customer with a lead
The lead API requires clickId, eventName, and customerExternalId. After signup succeeds, supply the cookie's click ID and the customer ID in your database. With the Node SDK this corresponds to dub.track.lead(body); with REST, to POST https://api.dub.co/track/lead. This is a request-body illustration; the downloadable sample contains no API client.
{
"clickId": "synthetic_click_001",
"eventName": "Sign up",
"customerExternalId": "demo_user_001",
"mode": "wait"
}mode: "wait" waits until the lead is recorded. If a sale follows immediately, check the lead response and ordering before continuing. The docs deduplicate the combination of customerExternalId and eventName: only the first lead is recorded, and duplicates return null. Successful requests and newly recorded leads are different counts.
When the click ID is missing, our sample returns an empty plan with missing-click-id. Dub also documents deferred leads and direct sales; this example covers a signup with an existing click ID followed by a purchase. Do not replace missing data with another ID or zero. Check cookie persistence, the handoff to signup, and the target link.
Send a sale with the correct USD or JPY units
The sale API requires customerExternalId and amount. In a lead-first workflow, use the same internal customer ID. An explicit leadEventName associates the sale with a particular lead and is case-sensitive. Here it matches Sign up.
{
"customerExternalId": "demo_user_001",
"amount": 1999,
"currency": "usd",
"eventName": "Invoice paid",
"invoiceId": "demo_invoice_001",
"leadEventName": "Sign up",
"paymentProcessor": "custom"
}Pass 1999 for USD 19.99 and 1580 for JPY 1580. The API uses cents for two-decimal currencies and full integer amounts for zero-decimal currencies. Do not multiply every currency by 100. Take the confirmed amount from your payment system rather than trusting an arbitrary browser value as revenue.
invoiceId can prevent duplicate sales for the same invoice. Use a stable payment-system ID instead of creating a new one on each retry. That is the documented API contract; our local sample does not verify Dub's deduplication. It neither sends nor stores events, and only checks that identical inputs produce identical JSON.
Map customers correctly with the Stripe integration
The Dub Stripe guide requires Business or above, installation of the Stripe app, and connection to a Dub workspace. Its Checkout example passes the internal customer ID used for the lead in Session metadata.dubCustomerExternalId. Do not substitute client_reference_id: the official example uses teamId there.
// Metadata for your existing Checkout Session creation flow
const metadata = { dubCustomerExternalId: user.id }The integration listens to events including invoice.paid. The guide warns that guest checkout with a null customer does not track sales and says to set customer_creation: "always". If you adopt the integration, design the flow to avoid also sending a manual sale for the same payment. This article does not implement Stripe payments or webhook reception. Verify a real connection separately in an authorized test environment, such as the documented Stripe sandbox and Dub staging workspace.
Reproduce the request JSON without credentials
Save dub-conversion-demo.mjs and run it with Bun or Node.js 22+. It needs no dependencies or API keys and performs no network calls or file writes.
bun dub-conversion-demo.mjs
# or
node dub-conversion-demo.mjsusd.requests contains the ordered lead and sale JSON; jpy.requests uses JPY amount 1580. missingClick returns status: "skipped", empty requests, and networkRequests: 0. The sample permits only USD and JPY. It rejects negative, fractional, and unsafe integer amounts, plus empty customer and invoice IDs; zero is accepted. These are local sample restrictions, not Dub's complete validation or currency support.
A production sender should use existing approved server authentication and inspect API responses and errors. The sample's networkRequests: 0 is not a delivery-success count. The older shared demo covers Circleback and UTMs and is not needed here.
Keep the measurements separate
Record article views, referral clicks, leads, sales, and confirmed commissions separately. Building JSON does not establish tracking success or revenue. When an authorized end-to-end test is available, reconcile customer IDs, invoice IDs, currency units, and retry results.
For referral-ID preservation during URL construction, see the Dub UTM article. For handing meeting tasks to the next workflow, see the Circleback webhook guide.
Frequently asked questions
Which plan supports Dub conversion tracking?
The official guide checked on October 21, 2026 requires Business or above. Enable conversion tracking on the link, preserve dub_id with Dub Analytics, and send lead and sale events from your server. We have not tested enrollment or a live connection.
What goes in clickId and customerExternalId?
clickId is the click ID issued by Dub, normally read from the dub_id cookie. customerExternalId is the customer ID in your own database. Do not invent a click ID from UTMs or an article slug; use the same customer ID at signup and purchase.
Should I multiply JPY sale amounts by 100?
The official API uses cents for two-decimal currencies such as USD and full integer amounts for zero-decimal currencies such as JPY. Send 1999 for USD 19.99 and 1580 for JPY 1580. The sample accepts only usd and jpy.
What happens if I resend a lead or sale?
The docs deduplicate leads by customerExternalId and eventName, returning null for duplicates. Supply a stable invoiceId for the same invoice when tracking sales. We have not tested server-side deduplication on Dub.
How do I map a Stripe Checkout customer to Dub?
For the official Dub Stripe integration, set Checkout Session metadata.dubCustomerExternalId to the same internal customer ID used for the lead. Install and connect the integration first. For guest checkout with a null customer, the guide says to set customer_creation: always. We have not tested the Stripe connection.
FAQ
- Q. Which plan supports Dub conversion tracking?
- The official guide checked on October 21, 2026 requires Business or above. Enable conversion tracking on the link, preserve dub_id with Dub Analytics, and send lead and sale events from your server. We have not tested enrollment or a live connection.
- Q. What goes in clickId and customerExternalId?
- clickId is the click ID issued by Dub, normally read from the dub_id cookie. customerExternalId is the customer ID in your own database. Do not invent a click ID from UTMs or an article slug; use the same customer ID at signup and purchase.
- Q. Should I multiply JPY sale amounts by 100?
- The official API uses cents for two-decimal currencies such as USD and full integer amounts for zero-decimal currencies such as JPY. Send 1999 for USD 19.99 and 1580 for JPY 1580. The sample accepts only usd and jpy.
- Q. What happens if I resend a lead or sale?
- The docs deduplicate leads by customerExternalId and eventName, returning null for duplicates. Supply a stable invoiceId for the same invoice when tracking sales. We have not tested server-side deduplication on Dub.
- Q. How do I map a Stripe Checkout customer to Dub?
- For the official Dub Stripe integration, set Checkout Session metadata.dubCustomerExternalId to the same internal customer ID used for the lead. Install and connect the integration first. For guest checkout with a null customer, the guide says to set customer_creation: always. We have not tested the Stripe connection.