SaaS and B2B
Best practices for SaaS growth teams, B2B marketers, and demand-gen operators using 10x to capture leads, run lifecycle retargeting, and govern multi-team link operations.
Business problems
- Lead attribution breaks across channels — A prospect clicks an ad, reads a blog post, then signs up on a different session. The original source is lost.
- No lifecycle retargeting without heavy tooling — Triggering behavior-based follow-ups requires integrating 3-4 tools (analytics, email, CRM, link management).
- Campaign setup bottleneck — Every new campaign requires coordination between marketing, ops, and engineering to wire up tracking.
- Multi-team link chaos — Multiple teams create links with no naming conventions, overlapping slugs, and no audit trail.
- Custom domain delays — Enterprise clients need branded short domains, but DNS setup and verification stall launches.
Feature-to-problem map
| Problem | 10x feature | Guide |
|---|---|---|
| Broken attribution | ctx token with session binding + conversion API | Context Token Lifecycle |
| No lifecycle tooling | Chain signals + prefetch + webhook delivery | Chain Signals and Prefetch |
| Campaign setup bottleneck | Campaign API with LEAD_CAPTURE goal + auto-defaults | Campaign Funnel Workflow |
| Multi-team link chaos | Collaborators with role hierarchy + audit events | Collaborators and Ownership |
| Custom domain delays | Domain reconciliation API | Custom Domains |
Recommended workflows
1. Set up a lead-capture campaign with one API call
Create a campaign with lead-capture defaults:
POST /v2/handles/{handle}/campaigns
{
"campaignId": "webinar-q3",
"goalType": "LEAD_CAPTURE",
"status": "ACTIVE",
"primaryLink": {
"slug": "webinar",
"destinationUrl": "https://app.example.com/webinar-signup",
"title": "Q3 Product Webinar"
}
}
The LEAD_CAPTURE goal auto-sets the tracking preset to ga4_lead and the conversion event to lead. No manual configuration needed.
On your signup success page, fire the conversion:
POST /v2/public/conversions
{
"ctx": "<token from redirect>",
"eventType": "lead",
"idempotencyKey": "signup-user@example.com"
}
Use the email as the idempotency key to prevent duplicate lead records from form resubmissions.
2. Route traffic by device and segment
B2B traffic behaves differently on mobile vs. desktop. Route desktop users to the full demo page and mobile users to a lighter signup form:
PUT /v2/handles/{handle}/links/webinar
{
"destinationUrl": "https://app.example.com/webinar-signup",
"routingRules": [
{
"priority": 10,
"destinationUrl": "https://app.example.com/webinar-signup?view=desktop",
"conditions": { "deviceIn": ["DESKTOP"] }
},
{
"priority": 20,
"destinationUrl": "https://app.example.com/webinar-signup?view=mobile",
"conditions": { "deviceIn": ["MOBILE", "TABLET"] }
}
]
}
Combine with segment targeting to show returning visitors a different experience:
{
"priority": 5,
"destinationUrl": "https://app.example.com/webinar-returning",
"conditions": { "segmentIn": ["engaged_prospect"] }
}
The engaged_prospect segment is auto-derived when a visitor's engagement score reaches 0.75 or higher based on accumulated click history.
3. Retarget prospects with chain signals
When a prospect visits your pricing page but does not convert, record a signal:
POST /v2/public/chain/signals
{
"handle": "yourapp",
"sessionId": "sig_yourapp_1740500000000_abc123",
"eventType": "pricing_view",
"signals": [
{ "signalKey": "page", "signalValue": "pricing", "confidence": 1.0 },
{ "signalKey": "plan_interest", "signalValue": "pro", "confidence": 0.8 }
]
}
Create a chain rule to show targeted messaging when they visit again:
{
"ruleId": "pricing-followup",
"enabled": true,
"priority": 100,
"triggerEvent": "page_view",
"chainConditions": {
"require": [
{ "signalKey": "page", "operator": "equals", "signalValue": "pricing" },
{ "signalKey": "plan_interest", "operator": "exists" }
],
"exclude": [
{ "signalKey": "signup_complete", "operator": "exists" }
],
"maxChainAgeMinutes": 10080
},
"action": {
"type": "show_banner",
"template": "Still evaluating the {{plan_interest}} plan? Book a 15-min demo."
}
}
The 10,080-minute window (7 days) ensures the rule only fires for recent pricing page visitors.
Use prefetch to pre-compute decisions for multiple events in a single call:
POST /v2/public/chain/prefetch
{
"handle": "yourapp",
"sessionId": "sig_yourapp_1740500000000_abc123",
"triggerEvents": ["page_view", "exit_intent", "idle_60s"]
}
The response includes a decision for each event, cached for 60 seconds. Your client-side code can act on the right one without additional API calls.
4. Govern multi-team link operations
Add team members with appropriate roles:
- CREATOR — Marketing managers who create and manage links and campaigns.
- OPERATOR — Ops leads who manage webhooks, PAT tokens, and audit events.
- OWNER — Account admin who controls billing, collaborators, and ownership transfer.
Use the collaborators API to manage team members. The endpoint accepts a full array of collaborators (this is a replacement, not a patch):
PUT /v2/handles/{handle}/collaborators
[
{ "userId": "user_abc123", "role": "OPERATOR" },
{ "userId": "user_def456", "role": "CREATOR" }
]
Monitor changes via audit events:
GET /v2/handles/{handle}/audit-events
Audit events are retained for 90 days and cover collaborator changes, link imports, ownership transfers, and billing updates.
Set up webhooks to notify your team Slack channel on important events:
POST /v2/handles/{handle}/webhooks
{
"endpointUrl": "https://hooks.slack.com/services/T.../B.../xxx",
"eventTypes": ["campaign.health_changed", "destination_domain.blocked"]
}
5. Set up a custom branded domain
Add your domain and trigger reconciliation:
POST /v2/account/domains
{ "domain": "go.example.com" }
After adding the required DNS records (CNAME or A), trigger verification:
POST /v2/account/domains/go.example.com/reconcile
Reconciliation is asynchronous. Poll the domain status until it shows ready. This may take multiple attempts as DNS propagation completes.
Once ready, your links resolve at https://go.example.com/{slug} instead of the default 10x.in subdomain.
Key metrics to track
| Metric | Where to find it | What it tells you |
|---|---|---|
| Lead volume | GET /v2/handles/{handle}/analytics?funnel=true&campaignId={id} | Conversions per campaign |
| Source attribution | GET /v2/handles/{handle}/analytics?groupBy=referrer&campaignId={id} | Which channels drive leads |
| Device split | GET /v2/handles/{handle}/analytics?groupBy=device | Desktop vs. mobile conversion |
| Retargeting effectiveness | Chain signal depth + conversion after signal | Whether lifecycle rules recover leads |
| Data confidence | Funnel response confidence field | Reliability of attribution data |
Common mistakes
- Using raw clicks for conversion rate. B2B traffic often has bot noise from security scanners and link previews. Always use
qualifiedClicksfor accurate rates. - Not separating campaigns by channel. Create distinct campaigns for paid, organic, email, and partner channels. Mixing them makes attribution meaningless.
- Setting chain rule windows too short. B2B sales cycles are long. A 24-hour window misses most prospects. Use 7-14 day windows (
maxChainAgeMinutes: 10080-20160). - Giving all team members OWNER role. Follow the principle of least privilege. Most team members need CREATOR, not OWNER. Reserve OWNER for account administrators.
- Starting domain reconciliation before DNS propagation. Wait at least 15 minutes after adding DNS records before triggering reconciliation. Premature attempts will fail.
Related: