Unauthorized |
401
Execute Linktree secondary workflow: Analytics & Insights.
ReadWriteEditBash(npm:*)Grep
Linktree — Analytics & Reporting
Overview
Pull click tracking data, measure conversion rates, and extract audience insights from
your Linktree profile. Use this workflow when you need to understand which links drive
traffic, where your audience comes from, and how engagement trends over time. This is
the secondary workflow — for link creation and profile management, see
linktree-core-workflow-a.
Instructions
Step 1: Fetch Overall Profile Analytics
const analytics = await client.analytics.get({
profile_id: profile.id,
period: 'last_30_days',
granularity: 'daily',
});
console.log(`Total views: ${analytics.views}`);
console.log(`Total clicks: ${analytics.clicks}`);
console.log(`CTR: ${(analytics.clicks / analytics.views * 100).toFixed(1)}%`);
analytics.daily.forEach(d =>
console.log(` ${d.date}: ${d.views} views, ${d.clicks} clicks`)
);
Step 2: Rank Per-Link Performance
const linkStats = await client.analytics.byLink({
profile_id: profile.id,
period: 'last_7_days',
sort: 'clicks_desc',
});
linkStats.forEach((s, i) =>
console.log(`#${i + 1} ${s.title}: ${s.clicks} clicks (${s.unique_visitors} unique, CTR ${s.ctr}%)`)
);
Step 3: Pull Audience Demographics
const audience = await client.analytics.audience({
profile_id: profile.id,
period: 'last_30_days',
});
console.log('Top locations:', audience.locations.slice(0, 5).map(l => `${l.country}: ${l.pct}%`));
console.log('Top referrers:', audience.referrers.slice(0, 5).map(r => `${r.source}: ${r.visits}`));
console.log('Devices:', `Mobile ${audience.devices.mobile}% / Desktop ${audience.devices.desktop}%`);
Step 4: Generate a Conversion Report
const report = await client.analytics.report({
profile_id: profile.id,
period: 'last_30_days',
metrics: ['views', 'clicks', 'ctr', 'unique_visitors', 'top_referrers'],
format: 'json',
});
console.log(`Report period: ${report.start_date} to ${report.end_date}`);
console.log(`Highest CTR link: ${report.top_link.title} (${report.top_link.ctr}%)`);
console.log(`Total unique visitors: ${report.unique_visitors}`);
Error Handling
| Issue |
Cause |
Fix |
401 Unauthorized |
Expired or invalid bearer token |
Re-authenticate via OAuth flow |
403 Analytics unavailable |
Free-tier profile without analytics |
Upgrade to Pro plan for analytics access |
404 Profile not found |
Wrong profile_id |
Verify with client.profiles.list(
Cost Tuning for Linktree.
ReadWriteEditGrep
Linktree Cost Tuning
Overview
Linktree uses tiered pricing (Free, Starter, Pro, Premium) with cost scaling driven by analytics API call volume and link event tracking. Every page view, link click, and analytics query generates API activity. For brands managing multiple Linktree profiles or high-traffic pages with thousands of daily clicks, redundant analytics polling and uncached link data lookups create unnecessary API spend. Optimizing retrieval patterns and choosing the right tier based on actual feature usage prevents overpaying for unused premium capabilities.
Cost Breakdown
| Component |
Cost Driver |
Optimization |
| Plan tier |
Monthly subscription (Starter $5, Pro $9, Premium $24) |
Audit feature usage — downgrade if premium features unused |
| Analytics API calls |
Per-request for click/view data |
Cache analytics responses; poll on 15-min intervals max |
| Link event tracking |
Volume of click events across all links |
Aggregate events client-side before API submission |
| Profile API reads |
Repeated fetches of link tree structure |
Cache profile data with 5-min TTL; structure changes rarely |
| Webhook deliveries |
Events pushed per link interaction |
Filter low-value events; batch webhook processing |
API Call Reduction
class LinktreeAnalyticsCache {
private cache = new Map<string, { data: any; expiry: number }>();
private readonly minPollInterval = 900_000; // 15 minutes
private lastPoll = 0;
async getAnalytics(profileId: string, fetchFn: () => Promise<any>): Promise<any> {
const cacheKey = `analytics:${profileId}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() < cached.expiry) return cached.data;
if (Date.now() - this.lastPoll < this.minPollInterval) {
return cached?.data ?? null; // Return stale data rather than over-polling
}
this.lastPoll = Date.now();
const data = await fetchFn();
this.cache.set(cacheKey, { data, expiry: Date.now() + this.minPollInterval });
return data;
}
async getProfile(profileId: string, fetchFn: () => Promise<any>): Promise<any> {
const cacheKey = `profile:${profileId}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() < cached.expiry) return cached.data;
const data = await fetchFn();
this.cache.set(cacheKey, { data, expiry: Date.now() + 300_000 }); // 5-min TTL
return data;
}
}
Usage Monitoring
class LinktreeCostTracker {
private dailyCalls = { analytics: 0, profile: 0, events: 0 };
private budgets = { analytics: 1000, profile: 500, events: 5000 };
record(type: '
Debug Bundle for Linktree.
ReadBash(curl:*)Grep
Linktree Debug Bundle
Overview
This debug bundle collects diagnostic evidence from Linktree link-in-bio API integrations
for troubleshooting profile rendering, link analytics, and webhook delivery issues. It
captures OAuth token validity, profile metadata retrieval, individual link status checks,
click analytics availability, and webhook endpoint health. The resulting tarball gives
support engineers the data needed to diagnose broken links, missing analytics events,
profile sync failures, and API permission issues without requiring Linktree admin access.
Prerequisites
curl, jq, tar installed
LINKTREEAPIKEY set (OAuth bearer token from Linktree developer portal)
Debug Collection Script
#!/bin/bash
set -euo pipefail
BUNDLE="debug-linktree-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE"
# Environment check
echo "=== Environment ===" > "$BUNDLE/environment.txt"
echo "API Key: ${LINKTREE_API_KEY:+SET (redacted)}" >> "$BUNDLE/environment.txt"
echo "Node: $(node -v 2>/dev/null || echo 'not installed')" >> "$BUNDLE/environment.txt"
echo "Timestamp: $(date -u)" >> "$BUNDLE/environment.txt"
# API connectivity — user profile
echo "=== API Health ===" > "$BUNDLE/api-health.txt"
curl -sf -o "$BUNDLE/api-health.txt" -w "HTTP %{http_code} in %{time_total}s\n" \
-H "Authorization: Bearer ${LINKTREE_API_KEY}" \
"https://api.linktree.com/v1/user" 2>&1 || echo "UNREACHABLE" > "$BUNDLE/api-health.txt"
# Profile links enumeration
echo "=== Links ===" > "$BUNDLE/links.json"
curl -sf -H "Authorization: Bearer ${LINKTREE_API_KEY}" \
"https://api.linktree.com/v1/links" \
>> "$BUNDLE/links.json" 2>&1 || echo '{"error":"FAILED"}' > "$BUNDLE/links.json"
# Link click analytics (last 7 days)
echo "=== Analytics ===" > "$BUNDLE/analytics.json"
curl -sf -H "Authorization: Bearer ${LINKTREE_API_KEY}" \
"https://api.linktree.com/v1/analytics?period=7d" \
>> "$BUNDLE/analytics.json" 2>&1 || echo '{"error":"ANALYTICS_FAILED"}' > "$BUNDLE/analytics.json"
# Recent logs
echo "=== Recent Logs ===" > "$BUNDLE/app-logs.txt"
tail -100 /var/log/linktree-sync/*.log >> "$BUNDLE/app-logs.txt" 2>/dev/null || echo "No sync logs found" >> "$BUNDLE/app-logs.txt"
# Rate limit status
echo "=== Rate Limits ===" > "$BUNDLE/rate-limits.txt"
curl -sI -H "Authorization: Be
Deploy Integration for Linktree.
ReadWriteEditGrep
Linktree Deploy Integration
Overview
Deploy a containerized Linktree integration service that synchronizes profile data, manages link collections, and handles appearance customizations through the Linktree API. This skill covers Docker multi-stage builds optimized for the Linktree SDK, environment configuration for API authentication, health checks that verify Linktree API connectivity, and rolling deployment strategies with zero-downtime link management updates.
Prerequisites
- Docker 24+ and Docker Compose v2 installed
- Valid
LINKTREEAPIKEY from the Linktree developer portal
- Node.js 20 LTS (build stage)
- Network access to
api.linktr.ee on port 443
- Target deployment host with at least 256MB available memory
Docker Configuration
FROM node:20-slim AS builder
WORKDIR /build
COPY package*.json tsconfig.json ./
RUN npm ci
COPY src/ ./src/
RUN npm run build
FROM node:20-slim
RUN groupadd -r linktree && useradd -r -g linktree -m appuser
WORKDIR /app
COPY --from=builder /build/dist ./dist/
COPY --from=builder /build/node_modules ./node_modules/
COPY package*.json ./
RUN npm prune --production
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
Environment Variables
LINKTREE_API_KEY="lt_live_xxxxxxxxxxxxx" # Linktree API key from developer portal
LINKTREE_BASE_URL="https://api.linktr.ee" # API base URL
LINKTREE_PROFILE_ID="" # Target profile identifier
LOG_LEVEL="info" # debug | info | warn | error
NODE_ENV="production"
PORT="3000"
Health Check Endpoint
import express from "express";
const app = express();
app.get("/health", async (_req, res) => {
try {
const response = await fetch(`${process.env.LINKTREE_BASE_URL}/v1/profile`, {
headers: { Authorization: `Bearer ${process.env.LINKTREE_API_KEY}` },
});
if (!response.ok) throw new Error(`Linktree API returned ${response.status}`);
const profile = await response.json();
res.json({ status: "healthy", profile: profile.username, links: profile.links?.length ?? 0 });
} catch (err) {
res.status(503).json({ status: "unhealthy", error: (err as Error).message });
}
});
Deployment Steps
Step 1: Build Image
docker build -t linktree-integration:$(git rev-parse --short HEAD) .
Step 2: Run Container
docker run -d --name linktree-svc \
--env-file .env.production \
-p 3000:3000 \
--restart unless-stopped \
lin
Create a minimal working Linktree example.
ReadWriteEditBash(npm:*)Grep
Linktree Hello World
Overview
Minimal working examples demonstrating core Linktree API functionality.
Instructions
Step 1: Get Profile
const profile = await client.profiles.get('myprofile');
console.log(`Bio: ${profile.bio}`);
console.log(`Links: ${profile.links.length}`);
Step 2: Create a Link
const link = await client.links.create({
profile_id: profile.id,
title: 'My Website',
url: 'https://example.com',
position: 0, // Top of list
thumbnail: 'https://example.com/icon.png'
});
console.log(`Created link: ${link.id}`);
Step 3: Update Link
await client.links.update(link.id, {
title: 'Updated Title',
archived: false
});
Step 4: List All Links
const links = await client.links.list({ profile_id: profile.id });
links.forEach(l => console.log(`${l.position}: ${l.title} → ${l.url}`));
Error Handling
| Error |
Cause |
Solution |
| Auth error |
Invalid credentials |
Check LINKTREEAPIKEY |
| Not found |
Invalid endpoint |
Verify API URL |
| Rate limit |
Too many requests |
Implement backoff |
Resources
Next Steps
See linktree-local-dev-loop.
Install and configure Linktree SDK/API authentication.
ReadWriteEditBash(npm:*)Bash(pip:*)Grep
Linktree Install & Auth
Overview
Set up Linktree API for programmatic link-in-bio management with 25M+ creators.
Prerequisites
- Linktree account and API access
- API key/credentials from Linktree dashboard
- Node.js 18+ or Python 3.8+
Instructions
Step 1: Install SDK
npm install @linktree/sdk
# or: pip install linktree-sdk
Step 2: Configure Authentication
export LINKTREE_API_KEY="your-api-key-here"
echo 'LINKTREE_API_KEY=your-api-key' >> .env
Step 3: Verify Connection (TypeScript)
import { LinktreeClient } from '@linktree/sdk';
const client = new LinktreeClient({ apiKey: process.env.LINKTREE_API_KEY });
const profile = await client.profiles.get('myprofile');
console.log(`Profile: ${profile.username} — ${profile.links.length} links`);
Step 4: Verify Connection (Python)
import linktree
client = linktree.Client(api_key=os.environ['LINKTREE_API_KEY'])
profile = client.profiles.get('myprofile')
print(f'Profile: {profile.username} — {len(profile.links)} links')
Error Handling
| Error |
Code |
Solution |
| Invalid API key |
401 |
Verify credentials in dashboard |
| Permission denied |
403 |
Check API scopes/permissions |
| Rate limited |
429 |
Implement backoff |
Resources
Next Steps
After auth, proceed to linktree-hello-world.
Local Dev Loop for Linktree.
ReadWriteEdit
Linktree Local Dev Loop
Overview
Local development workflow for Linktree link-in-bio API integration. Provides a fast feedback loop with mock link profiles, analytics, and appearance data so you can build social management tools without hitting the live Linktree API. Toggle between mock mode for rapid UI iteration and live mode for validating profile updates against the real Linktree platform.
Environment Setup
cp .env.example .env
# Set your credentials:
# LINKTREE_API_KEY=lt_xxxxxxxxxxxx
# LINKTREE_BASE_URL=https://api.linktr.ee/v1
# MOCK_MODE=true
npm install express axios dotenv tsx typescript @types/node
npm install -D vitest supertest @types/express
Dev Server
// src/dev/server.ts
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
const app = express();
app.use(express.json());
const MOCK = process.env.MOCK_MODE === "true";
if (!MOCK) {
app.use("/v1", createProxyMiddleware({
target: process.env.LINKTREE_BASE_URL,
changeOrigin: true,
headers: { Authorization: `Bearer ${process.env.LINKTREE_API_KEY}` },
}));
} else {
const { mountMockRoutes } = require("./mocks");
mountMockRoutes(app);
}
app.listen(3005, () => console.log(`Linktree dev server on :3005 [mock=${MOCK}]`));
Mock Mode
// src/dev/mocks.ts — realistic link-in-bio profile and analytics responses
export function mountMockRoutes(app: any) {
app.get("/v1/profile", (_req: any, res: any) => res.json({
id: "usr_1", username: "janedoe", displayName: "Jane Doe", bio: "Designer & creator",
links: [
{ id: "lnk_1", title: "Portfolio", url: "https://jane.design", position: 0, enabled: true },
{ id: "lnk_2", title: "Shop", url: "https://shop.jane.design", position: 1, enabled: true },
{ id: "lnk_3", title: "Newsletter", url: "https://jane.substack.com", position: 2, enabled: false },
],
}));
app.get("/v1/analytics", (_req: any, res: any) => res.json({
totalViews: 12450, totalClicks: 3280, clickRate: 0.263,
topLinks: [{ id: "lnk_1", title: "Portfolio", clicks: 1890 }],
}));
app.put("/v1/links/:id", (req: any, res: any) => res.json({ id: req.params.id, ...req.body, updatedAt: new Date().toISOString() }));
}
Testing Workflow
npm run dev:mock & # Start mock server in background
npm run test # Unit tests with vitest
npm run test -- --watch # Watch mode for rapid iteration
MOCK_MODE=false npm run test:integration # Integration test against real API
<
Optimize Linktree API integration performance with caching, batching, and rate limit strategies.
ReadWriteEditGrep
Linktree Performance Tuning
Overview
Linktree profiles are high-traffic read endpoints — a single creator's link-in-bio page can receive millions of hits during viral moments. This skill covers caching strategies tuned to Linktree's data volatility, batch link operations, and resilient rate limit handling to prevent stale data and API cost overruns.
Instructions
- Implement Redis caching (or in-memory Map for development) with product-specific TTLs
- Wrap all API calls with the rate limit handler before deploying to production
- Enable connection pooling and configure batch sizes based on your traffic volume
- Set up monitoring metrics and verify cache hit rates exceed 80%
Prerequisites
- Linktree API key with read/write scopes
- Redis instance (or Node.js in-memory cache for development)
- Monitoring stack (Prometheus/Grafana or equivalent)
- Node.js 18+ with native fetch support
Caching Strategy
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
// Profile data changes infrequently — cache 10 minutes
// Link lists update more often — cache 2 minutes
const TTL = { profile: 600, links: 120, analytics: 300 } as const;
async function getCachedProfile(username: string): Promise<LinktreeProfile> {
const key = `lt:profile:${username}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const profile = await linktreeApi.getProfile(username);
await redis.setex(key, TTL.profile, JSON.stringify(profile));
return profile;
}
async function getCachedLinks(profileId: string): Promise<LinktreeLink[]> {
const key = `lt:links:${profileId}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const links = await linktreeApi.getLinks(profileId);
await redis.setex(key, TTL.links, JSON.stringify(links));
return links;
}
Batch Operations
// Fetch multiple profiles in parallel with concurrency limit
import pLimit from "p-limit";
const limit = pLimit(5); // Max 5 concurrent Linktree API calls
async function batchFetchProfiles(usernames: string[]): Promise<LinktreeProfile[]> {
return Promise.all(
usernames.map((u) => limit(() => getCachedProfile(u)))
);
}
// Bulk link updates — group mutations into single request windows
async function batchUpdateLinks(
profileId: string,
updates: LinkUpdate[]
): Promise<void> {
const chunks = chunkArray(updates, 10); // 10 links per request
for (const chunk of chunks) {
await Promise.all(chunk.map((u) => limit(() => linktreeApi.updateLink(profileId, u))));
}
}
Connection Pooling
import { Agent } from "undici";
const linktreeAgent = new Agent({
connect: { timeout: 5
Prod Checklist for Linktree.
ReadWriteEdit
Linktree Production Checklist
Overview
Linktree profiles serve as the single gateway between a creator's social audience and their monetized destinations. A misconfigured integration can silently drop link-click analytics, leak API keys through client-side calls, or trip the 100 req/min rate limit during viral traffic spikes. This checklist hardens your Linktree API integration for production-grade reliability, ensuring click tracking stays accurate, webhook delivery remains verified, and your link-in-bio pages load under high concurrency.
Prerequisites
- Production Linktree API key (not sandbox/dev key)
- Secrets manager configured (Vault, AWS Secrets Manager, or GCP Secret Manager)
- Monitoring stack operational (Datadog, Grafana, or CloudWatch)
- Staging environment validated with synthetic traffic test
Authentication & Secrets
- [ ] API keys stored in vault/secrets manager (never in code or environment files)
- [ ] Key rotation schedule configured (every 90 days)
- [ ] Separate keys for staging vs production environments
- [ ] Bearer token included in Authorization header, not query params
- [ ] API key scopes restricted to minimum required permissions (read-only where possible)
API Integration
- [ ] Base URL points to
https://api.linktr.ee/v1 (production, not sandbox)
- [ ] Rate limiting enforced client-side at 90 req/min (buffer below 100 req/min hard limit)
- [ ] Pagination implemented for profile link listing (cursor-based, not offset)
- [ ] Request timeout set to 10 seconds for profile reads, 30 seconds for analytics queries
- [ ]
Content-Type: application/json and Accept headers set on every request
- [ ] Link click tracking webhook endpoint registered and reachable from Linktree servers
- [ ] Bulk link updates batched to avoid rate limit bursts during campaign launches
Error Handling & Resilience
- [ ] Circuit breaker configured for Linktree API calls (open after 5 consecutive failures)
- [ ] Retry logic with exponential backoff for 429 (rate limit) and 5xx responses
- [ ] 429 responses parse
Retry-After header to schedule next attempt
- [ ] Graceful degradation serves cached profile data when API is unreachable
- [ ] Link click events queued locally during outages and replayed on recovery
- [ ] Timeout errors distinguished from authentication errors in alerting
Monitoring & Alerting
- [ ] API latency tracked (p50, p95, p99) with 500ms p95 threshold
- [ ] Error rate alerts configured (threshold: >1% over 5-minute window)
- [ ] Rate limit headroom monitored (alert when usage exceeds 80 req/min sustained)
- [ ] Click tracking event delivery lag measured (alert if >60s behind real-time)
- [
Rate Limits for Linktree.
ReadWriteEdit
Linktree Rate Limits
Overview
Linktree's API enforces rate limits per OAuth token, with analytics endpoints throttled more aggressively than profile management operations. Agencies managing dozens of creator profiles need to stagger link updates and analytics pulls across accounts to avoid hitting per-token and global IP-based limits. Bulk link reordering and analytics export during campaign launches are the most common rate-limit triggers, especially when synchronizing link performance data with external dashboards on short polling intervals.
Rate Limit Reference
| Endpoint |
Limit |
Window |
Scope |
| Profile read/update |
60 req |
1 minute |
Per OAuth token |
| Link create/update/delete |
30 req |
1 minute |
Per OAuth token |
| Analytics summary |
20 req |
1 minute |
Per OAuth token |
| Analytics detailed (per-link) |
10 req |
1 minute |
Per OAuth token |
| Webhook management |
10 req |
1 minute |
Per OAuth token |
Rate Limiter Implementation
class LinktreeRateLimiter {
private tokens: number;
private lastRefill: number;
private readonly max: number;
private readonly refillRate: number;
private queue: Array<{ resolve: () => void }> = [];
constructor(maxPerMinute: number) {
this.max = maxPerMinute;
this.tokens = maxPerMinute;
this.lastRefill = Date.now();
this.refillRate = maxPerMinute / 60_000;
}
async acquire(): Promise<void> {
this.refill();
if (this.tokens >= 1) { this.tokens -= 1; return; }
return new Promise(resolve => this.queue.push({ resolve }));
}
private refill() {
const now = Date.now();
this.tokens = Math.min(this.max, this.tokens + (now - this.lastRefill) * this.refillRate);
this.lastRefill = now;
while (this.tokens >= 1 && this.queue.length) {
this.tokens -= 1;
this.queue.shift()!.resolve();
}
}
}
const linkLimiter = new LinktreeRateLimiter(25);
const analyticsLimiter = new LinktreeRateLimiter(8);
Retry Strategy
async function linktreeRetry<T>(
limiter: LinktreeRateLimiter, fn: () => Promise<Response>, maxRetries = 3
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
await limiter.acquire();
const res = await fn();
if (res.ok) return res.json();
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") || "30", 10);
const jitter = Math.random() * 2000;
await new Promise(r => setTimeout(r, retryAfter * 1000 + jitter));
continue;
}
if (res.status >= 500 && attemp
Reference Architecture for Linktree.
ReadWriteEditGrep
Linktree Reference Architecture
Overview
Design a read-optimized integration layer for the Linktree link-in-bio platform. The extreme read-to-write ratio on public profiles drives a two-tier cache with async analytics, keeping the hot path free from downstream blocking.
Instructions
- Provision the prerequisites below and configure Linktree API credentials.
- Deploy the service layer with the cache-aside pattern for profile reads.
- Wire the webhook ingester to invalidate cache on profile mutations.
- Start the click event consumer to populate the analytics store.
- Adjust Redis TTLs and rate-limit buckets to match your traffic profile.
Prerequisites
- Node.js 18+, TypeScript 5, Redis 7, RabbitMQ or SQS, PostgreSQL 15
- Linktree API credentials with
profile:read and links:write scopes
Architecture Diagram
Client --> API Gateway --> LinktreeService --> Linktree API
|
+--------------+--------------+
v v v
Redis Cache Event Queue Analytics DB
(profiles) (clicks/views) (aggregates)
Service Layer
class LinktreeService {
constructor(
private api: LinktreeApiClient,
private cache: ProfileCache,
private events: EventPublisher
) {}
async getProfile(username: string): Promise<Profile> {
const cached = await this.cache.get(`profile:${username}`);
if (cached) return cached;
const profile = await this.api.fetchProfile(username);
await this.cache.set(`profile:${username}`, profile, 300);
return profile;
}
async updateLinks(profileId: string, links: LinkUpdate[]): Promise<void> {
await this.api.patchLinks(profileId, links);
await this.cache.invalidate(`profile:${profileId}`);
await this.events.publish('links.updated', { profileId, count: links.length });
}
}
Caching Strategy
class ProfileCache {
constructor(private redis: RedisClient) {}
async get(key: string): Promise<Profile | null> {
const raw = await this.redis.get(key);
return raw ? JSON.parse(raw) : null;
}
async set(key: string, data: Profile, ttl: number): Promise<void> {
await this.redis.setEx(key, ttl, JSON.stringify(data));
}
async invalidate(pattern: string): Promise<void> {
const keys = await this.redis.keys(pattern);
if (keys.length) await this.redis.del(keys);
}
}
// TTLs: profiles 5 min, link lists 2 min, analytics summaries 15 min
Event Pipeline
class ClickEventConsumer {
constructor(private queue: MessageQueue, private db: AnalyticsStore) {}
async start(): Promise<void> {
await this.queue.
Sdk Patterns for Linktree.
ReadWriteEdit
Linktree SDK Patterns
Overview
Linktree's REST API exposes profile management, link CRUD, click analytics, and appearance theming through bearer-token authentication. A structured SDK client matters here because Linktree enforces strict per-minute rate limits on analytics endpoints and returns nested profile objects that benefit from strong typing. These patterns provide a thread-safe singleton, typed error classification, fluent request building for paginated link lists, and test utilities for mocking profile and analytics responses.
Prerequisites
- Node.js 18+, TypeScript 5+
LINKTREEAPIKEY environment variable (generated in Linktree admin > Settings > Developer)
axios or node-fetch for HTTP transport
Singleton Client
interface LinktreeConfig {
apiKey: string;
baseUrl?: string;
timeout?: number;
}
let client: LinktreeClient | null = null;
export function getLinktreeClient(overrides?: Partial<LinktreeConfig>): LinktreeClient {
if (!client) {
const config: LinktreeConfig = {
apiKey: process.env.LINKTREE_API_KEY ?? '',
baseUrl: 'https://api.linktr.ee/v1',
timeout: 10_000,
...overrides,
};
if (!config.apiKey) throw new Error('LINKTREE_API_KEY is required');
client = new LinktreeClient(config);
}
return client;
}
Error Wrapper
interface LinktreeError { status: number; code: string; detail: string; }
async function safeLinktree<T>(fn: () => Promise<T>): Promise<T> {
try { return await fn(); }
catch (err: any) {
const parsed: LinktreeError = {
status: err.response?.status ?? 500,
code: err.response?.data?.error?.code ?? 'UNKNOWN',
detail: err.response?.data?.error?.message ?? err.message,
};
if (parsed.status === 429) {
const retryAfter = parseInt(err.response?.headers?.['retry-after'] ?? '5', 10);
await new Promise(r => setTimeout(r, retryAfter * 1000));
return fn();
}
if (parsed.status === 401) throw new Error(`Auth failed: ${parsed.detail}`);
throw new Error(`Linktree ${parsed.code} (${parsed.status}): ${parsed.detail}`);
}
}
Request Builder
class LinkQueryBuilder {
private params: Record<string, string> = {};
forProfile(id: string) { this.params.profile_id = id; return this; }
active(only = true) { this.params.is_active = String(only); return this; }
page(cursor: string) { this.params.cursor = cursor; return this; }
limit(n: number) { this.params.limit = String(Math.min(n, 100)); return this; }
build(): URLSearchParams { return new URLSearchParams(this.params); }
}
Response Types
interface LinktreeProfile
Security Basics for Linktree.
ReadWriteEdit
Linktree Security Basics
Overview
Linktree integrations handle user-generated content (link titles, URLs, bios) and analytics data that is PII-adjacent — click counts, geographic breakdowns, and referrer URLs can fingerprint individual visitors. Bearer token authentication means a leaked key grants full account access including link creation, profile modification, and analytics export. Webhook payloads carry real-time event data signed with HMAC-SHA256, and failing to verify signatures opens your endpoint to spoofed events and data poisoning.
Prerequisites
- Secrets manager (AWS SSM, GCP Secret Manager, or Vault) for all Linktree credentials
- HTTPS enforced on all webhook receiver endpoints
.env files in .gitignore — never committed to version control
- Logging infrastructure that supports field-level redaction
API Key Management
// Load Linktree bearer token from environment — never hardcode
const LINKTREE_TOKEN = process.env.LINKTREE_API_KEY;
function validateLinktreeConfig(): void {
if (!LINKTREE_TOKEN || LINKTREE_TOKEN.startsWith('lt_test_')) {
throw new Error('Missing or test-only LINKTREE_API_KEY — set a production token');
}
}
function linktreeHeaders(): Record<string, string> {
return {
Authorization: `Bearer ${LINKTREE_TOKEN}`,
'Content-Type': 'application/json',
};
}
// Call validateLinktreeConfig() at startup, before accepting requests
Webhook Signature Verification
import crypto from 'node:crypto';
const WEBHOOK_SECRET = process.env.LINKTREE_WEBHOOK_SECRET!;
function verifyLinktreeWebhook(payload: string, signature: string): boolean {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload, 'utf8')
.digest('hex');
// Timing-safe comparison prevents timing attacks
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Express middleware
app.post('/webhooks/linktree', (req, res) => {
const sig = req.headers['x-linktree-signature'] as string;
if (!sig || !verifyLinktreeWebhook(JSON.stringify(req.body), sig)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process verified event
});
Input Validation
import { URL } from 'node:url';
function sanitizeLinkTitle(title: string): string {
// Strip HTML/script tags from user-generated link titles
return title.replace(/<[^>]*>/g, '').trim().slice(0, 150);
}
function validateLinkUrl(url: string): boolean {
try {
const parsed = new URL(url);
// Only allow http/https — block javascript:, data:, file: schemes
return ['http:', 'https:'].includes(parsed.protocol);
}
Upgrade Migration for Linktree.
ReadWriteEdit
Linktree Upgrade & Migration
Overview
Linktree provides a link-in-bio platform with APIs for managing profiles, links, and click analytics. The API exposes endpoints for CRUD operations on link trees, individual links, and analytics data. Tracking API changes is critical because Linktree's link schema evolves with new link types (commerce, scheduling, music), analytics response formats change with new metric dimensions, and profile customization fields expand — breaking integrations that sync link performance data to marketing dashboards or automate link management across multiple profiles.
Version Detection
const LINKTREE_BASE = "https://api.linktr.ee/v1";
async function detectLinktreeApiVersion(apiKey: string): Promise<void> {
const res = await fetch(`${LINKTREE_BASE}/profile`, {
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
});
const version = res.headers.get("x-linktree-api-version") ?? "v1";
console.log(`Linktree API version: ${version}`);
// Check for deprecated link type fields
const linksRes = await fetch(`${LINKTREE_BASE}/links`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await linksRes.json();
const knownTypes = ["classic", "header", "music", "video", "commerce", "scheduling"];
const activeTypes = [...new Set(data.links?.map((l: any) => l.type) ?? [])];
const unknown = activeTypes.filter((t: string) => !knownTypes.includes(t));
if (unknown.length) console.log(`New link types detected: ${unknown.join(", ")}`);
}
Migration Checklist
- [ ] Review Linktree developer changelog for API breaking changes
- [ ] Audit codebase for hardcoded link type enums (new types may be added)
- [ ] Verify analytics endpoint response structure (metrics, dimensions, date ranges)
- [ ] Check profile customization fields for new appearance options
- [ ] Update link creation payload if required fields were added
- [ ] Test link ordering API — sort mechanism may have changed
- [ ] Validate thumbnail/image upload endpoints for size or format changes
- [ ] Check OAuth token scopes if new permissions required for analytics
- [ ] Update webhook handlers for link click and profile view events
- [ ] Run analytics data export and compare old vs. new response shapes
Schema Migration
// Linktree links evolved: simple URL → typed link with metadata
interface OldLink {
id: string;
title: string;
url: string;
position: number;
active: boolean;
}
interface NewLink {
id: string;
title: string;
url: string;
type: "classic" | "header" | "music" | "video" | "commerce" | "
Webhooks Events for Linktree.
ReadWriteEditGrep
Linktree Webhooks & Events
Overview
Linktree emits real-time webhook events whenever links, profiles, or analytics milestones change. These events enable automations such as syncing new bio links to a CMS, triggering social media posts when a profile is updated, alerting marketing teams when traffic milestones are hit, and auditing link lifecycle changes for compliance dashboards. All payloads are JSON over HTTPS with HMAC-SHA256 signature verification to guarantee authenticity.
Prerequisites
- A registered Linktree developer app with webhook permissions enabled
- Webhook endpoint URL accessible over HTTPS (TLS 1.2+)
- Signing secret from the Linktree developer dashboard (
LINKTREEWEBHOOKSECRET)
- Express.js with
raw body parsing enabled for signature verification
Webhook Registration
import axios from "axios";
const res = await axios.post(
"https://api.linktr.ee/v1/webhooks",
{
url: "https://your-app.com/webhooks/linktree",
events: ["link.created", "link.updated", "link.deleted",
"profile.updated", "analytics.milestone"],
},
{ headers: { Authorization: `Bearer ${process.env.LINKTREE_API_TOKEN}` } }
);
console.log("Subscription ID:", res.data.id);
Signature Verification
import crypto from "crypto";
import { Request, Response, NextFunction } from "express";
function verifyLinktreeSignature(req: Request, res: Response, next: NextFunction) {
const signature = req.headers["x-linktree-signature"] as string;
const timestamp = req.headers["x-linktree-timestamp"] as string;
if (!signature || !timestamp) return res.status(401).send("Missing signature");
const payload = `${timestamp}.${(req as any).rawBody}`;
const expected = crypto
.createHmac("sha256", process.env.LINKTREE_WEBHOOK_SECRET!)
.update(payload)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(403).send("Invalid signature");
}
next();
}
Event Handler
app.post("/webhooks/linktree", verifyLinktreeSignature, (req, res) => {
const { type, data, timestamp } = req.body;
switch (type) {
case "link.created":
console.log(`New link: ${data.title} → ${data.url}`);
break;
case "link.updated":
console.log(`Link edited: ${data.link_id}, position: ${data.position}`);
break;
case "link.deleted":
console.log(`Link removed: ${data.link_id}`);
break;
case "profile.updated":
console.log(`Profile changed: bio=${data.bio}, avatar=${data.avatar_url
Ready to use linktree-pack?
|
|