Conversions API

Register conversions when users complete desired actions. For affiliate networks, use the server-to-server postback instead.

Endpoint

POST https://api.gettrack.link/api/conversions

No authentication: it is called from your own pages. It never reveals whether a click ID exists, so it can't be used to probe for valid IDs.

Request Body

FieldTypeRequiredDescription
click_idstringYesThe click ID from the tracked link (utm_click, or wherever you placed {click_id})
valuenumberNoRevenue for this conversion
currencystringNoISO code, defaults to USD when a value is sent
event_typestringNosale | lead | signup | any label
event_namestringNoHuman label, e.g. Checkout
order_idstringNoOrder ID. A second order from the same click counts separately
user_idstringNoYour internal user or customer ID
namestringNoThe customer's name
emailstringNoThe customer's email address
hoststringNoDomain where the conversion happened
page_urlstringNoPage where the conversion happened
metadatastringNoAnything else, as a JSON string

Example Request

curl -X POST "https://api.gettrack.link/api/conversions" \
  -H "Content-Type: application/json" \
  -d '{
    "click_id": "C9HW979UQ47Q69",
    "event_type": "sale",
    "value": 49.99,
    "currency": "USD",
    "order_id": "ord_987",
    "user_id": "cust_12345",
    "email": "[email protected]"
  }'

Responses

Status: 201 Created
{ "success": true }

The same response comes back for an unknown click ID and for a repeat of a conversion already recorded. Only a malformed body is rejected:

HTTP 400
{
  "statusCode": 400,
  "message": ["click_id must be a string"],
  "error": "Bad Request"
}

JavaScript Example

async function registerConversion(clickId, order) {
  await fetch('https://api.gettrack.link/api/conversions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      click_id: clickId,
      event_type: 'sale',
      value: order.total,
      currency: order.currency,
      order_id: order.id,
    }),
  });
}

// t.js stores the click ID under this key when the visitor lands
const clickId = localStorage.getItem('link_track_id');
if (clickId) {
  await registerConversion(clickId, { total: 49.99, currency: 'USD', id: 'ord_987' });
  localStorage.removeItem('link_track_id');
}

Reading conversions back

Recording is public and unauthenticated (anyone with a click ID can report a conversion for it). Reading needs an API key, and lives on the v1 API:

GET https://api.gettrack.link/api/v1/conversionsGET https://api.gettrack.link/api/v1/conversions/unmatched

The list takes link_id, status (pending, approved, declined, refunded), event, start_date, end_date, limit (100 max) and offset. Each row carries the click it belongs to, including the short click ID your network sends back. /unmatched returns postbacks whose click ID matched no click, kept for 90 days — nothing was recorded for those, so they are not conversions.

There is no write endpoint on v1: conversions come from your network's postback or the tracking script, never from a key. The same reads are available as tl conversions ls in the CLI and the list_conversions MCP tool, and the conversion.created and conversion.updated webhooks push each one as it happens.

Notes

  • A click can have several conversions: one per event type and order ID
  • Conversions appear on the link detail page and the Conversions page
  • Conversion rate is the share of clicks that produced at least one conversion
  • The click ID is a 14-character code; letter case doesn't matter