Short links cookbook
Every recipe here runs against the links surface (/api/v1/links) end to end — request in, decoded response out — so you can copy the exact envelope your integration receives. The concept guide (Short links with click tracking) explains the modes and quality signals; this page shows the wire format. Field and parameter definitions are in the links API reference.
Authenticate every request with X-API-Key. Run against the sandbox with a dv_test_sk_… key, then swap in your live key.
Task index
1. Mint a one-off tracked link
POST /api/v1/links mints a tracked link outside any send — use it when the URL goes into a surface you do not control through the Orbit send pipeline (a web page, a social bio, print collateral, a partner hand-off). Send the destination URL and, when the link belongs to a campaign, the campaign id so it lands on the per-campaign rollup.
cURL
201 Created returns the full decoded envelope:
data.codeis the 6-character public code. The shareable URL isshort_url—{short-domain}/l/{code}— andGET /l/{code}is the anonymous, rate-limited public redirect (mounted at the API host root, outside/api/v1). On a tenant with a branded domain the same mint returnshttps://go.yourbrand.com/l/gH4kM2.data.campaign_idis the attribution handle. Links minted without it drift out of the per-campaign rollup (recipe 3), even though their clicks still count tenant-wide.- Only
http://andhttps://URLs mint. A missing, non-string, or unparsableurlanswers422; so does ajavascript:ordata:target. The write rate limit is 20 requests per minute.
2. Send an SMS with inline shortening
You do not have to mint links yourself before a send. Passmetadata.shorten_urls: true on the SMS send and the pipeline rewrites every http(s):// URL in the body into a tracked short link before the message leaves — stamping the campaign and message ids onto each minted link so clicks attribute back to this send.
cURL
campaign_id and message_id from the send, so the click resolves back to this recipient (recipe 3). Two guards matter on SMS:
- Net-shortening only. On SMS a URL is swapped only when the minted short URL is strictly shorter than the original — shortening on SMS exists to cut segment cost, so an equal-or-longer swap is refused and the original URL stays verbatim. On non-SMS channels every
http(s)URL shortens regardless of length, because there the point is click analytics, not length. - Mint failures never fail the send. Shortening is fire-and-forget per URL: if the mint fails, the original URL ships untouched and the send proceeds.
sms_auto_shorten_urls, default ON) still rewrites URLs longer than 30 characters. The explicit metadata.shorten_urls: true opt-in is for URLs below that floor.
3. Read campaign, per-contact, and tenant analytics
Three read surfaces answer three different questions. All clamp out-of-rangewindow_days / top_limit values instead of rejecting them, and every top_links row re-attaches the resolved short_url so you can display it without reconstructing the domain.
Per-campaign rollup — “did this campaign’s links drive clicks?”
cURL
verified_clicks for human-only CTR — link-preview fetches and scanners inflate raw clicks by design. unique_attribution_rate is unique clicker recipients over attributed recipients (the recipients the campaign’s tracked messages reached); both derived rates report 0 when the denominator is empty, never NaN.
Per-contact history — “which links did THIS recipient tap?”
cURL
limit (1–500, default 100). Clicks resolve to a contact through the originating message, so an anonymous click on a link that was never message-attributed never appears here.
Tenant-wide insights — one call for the dashboard KPI row
cURL
GET /api/v1/links/insights bundles summary KPIs, the top-N ranking, and a day-by-day click timeline in one call — the same aggregates that power Insights → Link tracking in the dashboard. Pass campaign_id to restrict the whole bundle to one campaign.
4. Build and publish a landing page
Landing pages are no-code microsites built from a block list, served under the same/links base path. A page starts in draft; publishing mints its trackable short URL, so clicks attribute exactly like any other tracked link.
cURL
status is draft, short_url and render_url stay null — the page has no public address. A slug you pass is suffixed on collision (may-promo-3fka) so it never fails on a duplicate.
cURL
422. The minted short_url (/l/{code}) redirects to the internal render path (/lp/{code}), so the click-tracking pipeline covers landing-page traffic with no extra wiring. Per-page visits and conversions then roll up by campaign and by originating message:
cURL
5. Handle the short_link.click webhook
Subscribe an endpoint under Settings → Webhooks toshort_link.click and every tracked click POSTs to you in real time — the recipient-side sibling of email.clicked. Verify the X-Orbit-Signature header before trusting the body (one function call per Webhook consumer).
The decoded payload:
Node consumer
is_bot / quality_score (>= 0.5 means verified human) exactly the way the analytics endpoints do, so your CTR matches the dashboard. A missing user_agent scores 0.3 because a real browser essentially always sends one; quality_reason names the verdict (bot_user_agent | missing_user_agent). country is the edge-resolved ISO-3166-1 alpha-2 code, or null when the edge supplies no geo header. Delivery never blocks the redirect — a slow consumer cannot slow the clicker’s page load, and Orbit retries a failed POST on its own tail.
6. Pick a read surface
Raw clicks accumulate faster than most consumers should poll them. Match the question to the surface:
Rule of thumb: the webhook for streams, the analytics endpoints for aggregates, the delivery logs for forensics. Poll an aggregate endpoint on a schedule only when you genuinely cannot run an endpoint for the webhook.
Troubleshooting
- My mint returned the platform default host, not my branded domain. The tenant branded-domain value must be a bare
https://hostwith no path/query/fragment; a malformed value falls back to the default rather than failing. Fix the setting and re-mint — existing short URLs keep resolving. - A
422onPOST /api/v1/links.urlis missing, not a string, or fails URL parsing; a non-http(s)protocol is also refused. On landing pages,422at publish means the block list is empty. - Click counts look inflated on SMS or WhatsApp sends. Read
verified_clicks(or filter the webhook onquality_score >= 0.5). Preview fetchers inflate raw clicks by design; the raw count is kept so retro-filtering applies to already-collected data. - No
short_link.clickfor a WhatsApp template send. Template/broadcast sends carry no on-wire body to shorten — automatic tracking only fires on free-text bodies. Tracking inside a template’s URL has to live in the approved template itself.