OdyDocs

Link trackingnew

Read or change a workspace's tracked short links from code: every URL in an outbound text becomes link.ody.co/<code>, clicks surface in the thread, in Analytics and as a webhook. On by default.

Link tracking rewrites every URL in an outbound text — sent by a teammate, by the AI texting agent, by Astra during a call, or by your code through POST /messages — into a short link.ody.co/<code> link before it leaves (scheme-less in the text so it reads cleanly in a bubble; trackedLinks[].shortUrl and the link.clicked webhook carry the absolute https://link.ody.co/<code>). Tapping it 302s to the original URL with the workspace's UTM template appended, so the business's own website analytics attribute the visit to an Ody text. Human clicks are counted; previewers, scanners and prefetchers are filtered out so a click always means a person.

On by default (since 2026-11-07; PATCH { "enabled": false } opts a workspace out and that choice sticks). It is a workspace preference — the same three switches the app shows under Settings → Analytics — and this page is how you read or change them from code. Changes are immediate (the next text follows the new setting); texts already sent are unaffected either way and their short links keep working.

What you get back elsewhere
  • The stored message body is the text as sent (short links). Each activity carries the originals as trackedLinks[] with per-link click counts.
  • Every human click fires the link.clicked webhook (conversationId, contactId, linkId, destination host, clickCount, repeat, device, country).
  • In the app: a "… clicked your link" line in the thread (when showInThread), the conversation lifted to the top of the inbox on the first click, and a Link clicks block on the Analytics screen.

#The settings object

  • enabled boolean — Rewrite links in outbound texts as tracked short links. Default true; only a stored false turns it off.
  • showInThread boolean — Show a "… clicked your link" entry in the conversation and move it to the top of the inbox on the first click (never an unread badge). Clicks are counted either way. Default true.
  • utmTemplate string | null — Query string appended to every destination, k=v&k=v, ≤ 512 characters. null means the default: utm_source=ody&utm_medium=sms&utm_campaign={source}&utm_content=c_{contactRef}. Parameters already present on a link are never overwritten, so hand-built campaign links keep their attribution.

Template placeholders:

PlaceholderResolves to
{source}ody_text (a teammate) · ody_ai_text (the AI texting agent) · ody_ai_call_text (Astra during a call) · ody_api (this API / MCP)
{contactRef}An anonymous 8-character reference to the contact — never the phone number
{workspaceRef}An anonymous reference to the workspace
{line}The business number the text left from (E.164)
{code}The short link's code

Every response also carries defaults.utmTemplate (what null resolves to), baseUrl (https://link.ody.co) and placeholders.

#Read the settings

GET/v1/api/link-tracking

Scope: workspace:manage

curl https://api.ody.co/v1/api/link-tracking \
  -H "Authorization: Bearer $ODY_API_KEY"
Response
{
  "settings": { "enabled": true, "showInThread": true, "utmTemplate": null },
  "defaults": { "utmTemplate": "utm_source=ody&utm_medium=sms&utm_campaign={source}&utm_content=c_{contactRef}" },
  "baseUrl": "https://link.ody.co",
  "placeholders": ["source", "contactRef", "workspaceRef", "line", "code"]
}

#Turn it off or back on, show clicks in the thread, set the template

PATCH/v1/api/link-tracking

Scope: workspace:manage

Send only the fields you want to change. utmTemplate: null (or "") resets to the default.

  • enabled boolean
  • showInThread boolean
  • utmTemplate string | null

Errors — 400 invalid_argument when a field has the wrong type, when no recognised field is sent, or when the template is invalid (the message is the same sentence the app shows under the field: empty, longer than 512 characters, contains spaces / # / ?, a pair without =, or an unknown {placeholder}). 403 permission_denied when the key lacks workspace:manage.

curl -X PATCH https://api.ody.co/v1/api/link-tracking \
  -H "Authorization: Bearer $ODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
Response
{
  "settings": { "enabled": false, "showInThread": true, "utmTemplate": null },
  "defaults": { "utmTemplate": "utm_source=ody&utm_medium=sms&utm_campaign={source}&utm_content=c_{contactRef}" },
  "baseUrl": "https://link.ody.co",
  "placeholders": ["source", "contactRef", "workspaceRef", "line", "code"]
}

#What is — and isn't — rewritten

  • Outbound only. Links in texts your customers send are never changed.
  • Explicit http(s)://… URLs and bare domains with a recognised TLD (farmdogs.com/book); up to 5 links per text. Email addresses are not links.
  • Links to Ody itself (link.ody.co, api.ody.co) are left alone.
  • Codes are 7 random base62 characters — never sequential, so a code can't be used to enumerate the pages a business sends.
  • The redirect is always a 302 with Cache-Control: no-store and X-Robots-Tag: noindex; a cached or indexed redirect would hide clicks and leak destinations.
  • Test keys (ody_test_…) rewrite links too, and the short links resolve — you can watch the whole loop without texting anyone.

#Privacy

The click record stores the time, the device class (mobile / desktop / other), the country when the edge supplies it, and a daily-salted hash of the IP — never the IP itself. The attribution parameters identify the contact only by {contactRef}; no phone number ever reaches the destination.

#MCP

The same two operations are MCP tools: get_link_tracking and configure_link_tracking, gated by the same workspace:manage scope.

Last updated Sat Nov 07 2026 00:00:00 GMT+0000 (Coordinated Universal Time)Something wrong on this page? Tell us