Build a UTM tracking link with encoded campaign parameters
Create a ready-to-share campaign URL without manually joining question marks, ampersands, or percent-encoded values.
Run — free
Provide an absolute base URL and any combination of source, medium, campaign, term, and content. The builder preserves ordinary query parameters, replaces matching UTM parameters with the values you supply, keeps the fragment in its correct final position, and returns one valid link. It is useful for marketers working interactively and for developers who need the same predictable result inside publishing, email, advertising, and reporting workflows.
Start with an absolute destination and structured campaign fields
Begin with the complete destination, including its scheme and host, such as https://example.com/offers. A relative path like /offers is rejected because it has no independent origin and could resolve differently depending on where somebody opens it. Add the fields that describe the visit: source identifies the publisher or platform, medium describes the channel, and campaign groups related activity under one initiative. Term can record a paid-search keyword, while content can distinguish creatives, placements, buttons, or message variants. You may provide only the fields your measurement plan uses. The result writes them under the standard utm_source, utm_medium, utm_campaign, utm_term, and utm_content names. Keeping those values as separate inputs makes automation safer than accepting a partially assembled query string. It also makes naming conventions easier to review because each campaign property has a clear place before the final link is created. If the destination already contains an ordinary parameter such as a product identifier, referral code, or language choice, that parameter stays in the output instead of being discarded.
Let URL encoding protect spaces and reserved characters
Campaign labels frequently contain spaces, punctuation, slashes, ampersands, or characters outside basic ASCII. Those characters cannot always be copied into a query string literally without changing how a browser interprets the link. This builder uses standard URL and query serialization rules to encode every supplied value. For example, a campaign named July launch and a content label named hero CTA remain recognizable as values while becoming safe components of the returned URL. An ampersand inside a label is encoded as data rather than mistaken for the separator before another parameter. Existing query parameters are parsed and serialized through the same standards-based mechanism. When the base URL already has one of the five UTM keys and you supply a replacement, the supplied value wins; the builder does not create ambiguous duplicate keys for that field. A fragment such as #details remains after the complete query string, where browsers expect it. This behavior removes fragile hand-built concatenation logic and produces a deterministic result for the same input every time, with no network request or redirect involved.
Use consistent links in publishing and analytics workflows
A valid tagged link is only one part of reliable attribution. Decide a naming convention before generating large batches: teams often use lowercase source and medium values, stable campaign identifiers, and content labels tied to a creative or placement. The builder deliberately preserves the text you provide rather than silently changing case or inventing names, so governance remains under your control. Connect it to an email composer, social publishing queue, campaign spreadsheet, content management system, or internal launch checklist whenever links are produced. Store the structured inputs next to the returned URL so analysts can trace a dashboard label back to the campaign record that created it. Test the destination as part of the publishing process, especially when the original URL includes application-specific parameters. The capability does not shorten the link, contact the destination, verify that a page exists, or configure an analytics account; it only constructs the URL locally. That narrow scope makes it predictable and easy to combine with approval, link checking, shortening, or QR-code steps elsewhere in a workflow. Interactive browser use is free, while an automated API request costs $0.002.
What you can do with it
Tag newsletter destinations
Generate encoded links for buttons and text links while preserving product, language, and referral parameters already present in each destination.
Prepare social campaign links
Give each platform and creative a consistent source, medium, campaign, and content value before posts enter a publishing queue.
Automate paid campaign exports
Build final landing links from structured campaign records, including optional keyword terms and creative identifiers, without custom query-string concatenation.
FAQ
What does an automated request cost?
Each API request costs $0.002. You can also build a link directly in the browser on this page for free.
Which URL inputs are accepted?
The base URL must be absolute, meaning it includes a scheme and can be parsed independently. Relative paths are rejected.
What happens to existing query parameters?
They are preserved. If an existing UTM key is also supplied as an input field, the supplied value replaces that key.
Are spaces and special characters encoded?
Yes. Values are serialized with standard URL query rules so reserved characters remain part of the value instead of changing the query structure.
Must I provide all five UTM fields?
No. Source, medium, campaign, term, and content are individually optional; only the fields you provide are added or replaced.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/web/utm-link-build \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/launch?ref=homepage#details"}'const res = await fetch("https://api.kit.forhosting.com/web/utm-link-build", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"url": "https://example.com/launch?ref=homepage#details"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/utm-link-build",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"url": "https://example.com/launch?ref=homepage#details"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/utm-link-build", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"url":"https://example.com/launch?ref=homepage#details"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"url":"https://example.com/launch?ref=homepage#details"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/utm-link-build", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"url": "https://example.com/launch?ref=homepage#details"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.utm_link_build",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
timeout_sec | 30 |
max_crawl_pages | 25 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |