At some point a team stops making links by hand. The product needs a share link per user, the CMS should mint a short URL when a post is published, the campaign tool wants a tracked link per creative. A developer integration for creating trackable branded links turns the shortener into a function your systems call.
This guide walks the integration end to end: what to decide before you write code, how authentication and the create call work, how to make links trackable without breaking attribution, the redirect edge cases that bite, how webhooks fit, and how to test it. LinkPilot's API is the worked example; the shape applies to most platforms.
Decide the architecture first
Four decisions, made once, save most of the rework.
- Which systems create links, and on what trigger: a publish event, a user action, a campaign record being saved.
- Which fields callers control. Usually the destination and tags; sometimes a slug. Rarely the domain, which should be a per-workspace or per-client setting.
- How campaign data maps to tracking. The campaign, channel and placement in your system become tags on the link and UTM parameters on the destination. Decide the mapping and put it in one function.
- Direct link, deep link or fallback. A link into a mobile app needs a web fallback for people without the app. Decide whether the shortener handles that or your destination does.
Write these down. The integration is mostly glue, and the glue should be boring.
Authenticate with scoped keys
LinkPilot's API authenticates with bearer keys that start with
lp_live_, created in the dashboard and available on every plan,
including Free. Each key belongs to a workspace, so an agency
integration uses one key per client workspace and links land in the
right place by construction.
curl https://uselinkpilot.com/api/v1/me \
-H "Authorization: Bearer lp_live_..."
GET /me returns the workspace, plan, limits, current usage and the
key's rate budget. Call it on startup and surface the limits in your
own logs, so you find out about a cap before a campaign does.
Treat the key as a production credential: store it in a secrets manager, never in a repository or a front-end bundle, and when you have to hand it to someone building the integration, send it through a one-time secret link. The API key guide covers the handoff and rotation.
Create the link
The create call takes the destination and a few optional fields.
curl -X POST https://uselinkpilot.com/api/v1/links \
-H "Authorization: Bearer lp_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/launch?utm_source=newsletter&utm_medium=email&utm_campaign=oct-launch",
"slug": "oct-launch-email",
"domain": "go.example.com",
"title": "October launch, newsletter",
"tags": ["oct-launch", "email"]
}'
The response carries the link's ID, the short URL and the metadata you
sent. Store the ID beside your own record; it is what you use to
read, update or delete the link later. GET /links lists links
newest first with cursor pagination ({ data, next_cursor }), and
GET /links/{id} and DELETE /links/{id} act on one.
A few rules that keep the integration honest:
- Build the destination URL in code, UTM tags included, from your campaign fields. Hand-typed tags drift. The UTM playbook sets the conventions.
- Let the platform generate slugs unless a human will read them. When you do set slugs, derive them from campaign, channel and placement, lowercase with hyphens, and handle a conflict by appending a short suffix and retrying once.
- Make creation idempotent. Key each create on your own record ID
so a retry after a timeout finds the existing link instead of making
a second one. If the platform does not offer an idempotency key,
check
GET /linksfor your tag or slug before creating. - Bulk means a loop with backoff. Respect the rate budget from
/me, back off on 429, and log every failure with the record it belonged to.
For scripts and local tooling, the linkpilot CLI on npm wraps the
same calls, and the @uselinkpilot/mcp server exposes them to AI
agents. Both are open source on GitHub under TetraCoreHQ.
Make links trackable without breaking attribution
Each click on a link created by API is recorded exactly like a dashboard-made one: time, referrer, device, browser, country, region, a bot flag and a salted hash in place of the IP. That gives you the link-level count. Attribution on the destination still depends on the URL arriving intact.
- Preserve query parameters through the redirect. Test a link with the redirect checker and confirm the UTM tags are present on the final URL.
- Avoid chains. A short link that redirects to another redirect loses parameters and speed. Point at the final page.
- Decide what you collect. Country and device are aggregate analytics. A per-user link joined to a customer record is personal data. The modern link tracking guide lists what is recorded and what is deliberately not.
- Set expectations in the product. A click count is not a conversion count. Show both, labelled.
Handle the redirect edge cases
- Destination validation. Reject non-HTTPS destinations, your own shortener domain, and anything on a blocklist before you create the link. An open redirect on your brand is a phishing tool.
- Expired, disabled or missing links. Decide the fallback page when the link is created and set it, so a dead campaign link lands on the homepage rather than an error. For printed links, prefer editing the destination over ever letting it expire.
- Deep links. Use a destination that detects the app and falls back to the web, or a platform feature for it. Test on both operating systems with and without the app installed.
- Encoding. URL-encode the destination before you put it in the JSON, especially when it carries its own query string. Double encoding is the usual bug.
- Localisation. If one short link serves several languages, do the language detection on the destination, not in the slug.
Add webhooks for the events that matter
Webhooks let the rest of your stack react to link activity without
polling. LinkPilot signs each delivery with an HMAC signature header
and fires on link lifecycle (link.created, link.updated,
link.deleted) and secret lifecycle (secret.created,
secret.viewed, secret.burned). Verify the signature before
parsing, acknowledge quickly, process from a queue, and store event
IDs so retries are harmless. Click data is a reporting stream rather
than a webhook; read it through analytics and exports. The webhook
integrations
guide
covers building the receiver.
Test end to end, then keep watching
Before the integration touches a real campaign:
- Create a link with your test tags, in a staging workspace.
- Confirm the response has the ID and short URL you expect.
- Click it from a phone and a laptop. Confirm the redirect, the UTM tags on arrival and the two clicks in analytics with the right device types.
- Create the same record again and confirm you get the same link, not a duplicate.
- Delete it and confirm
link.deletedarrives at your webhook endpoint if you use one.
In production, watch three things: API error rates by status code,
redirect latency from a synthetic check, and a weekly reconciliation
of link counts between your records and GET /links. Rotate the API
key on a schedule and on every departure from the team that holds it.
Document the integration like an internal product: the fields it
accepts, the slug rule, the fallback behaviour, the limits from /me
and who to call when a campaign reports zero clicks. The next person
to touch it will be glad, and it is often you.