Endpoints
All requests require Authorization: Bearer atk_live_.... All responses are JSON.
Forwarding the real client
If you call from a reverse proxy, forward the end user’s IP and User-Agent via either:
- Headers:
X-Adtarget-Client-IP,X-Adtarget-Client-User-Agent - Or body fields:
clientIp,userAgent
Precedence: headers win over body fields.
POST /api/v1/events/visit
Mirror of the tracker’s POST /track/init. Creates a tracking_session + visit row.
Body (all fields optional unless noted):
{
tempId?: string, // UUID; generated if absent
clientSessionId?: string, // generated if absent
externalUserId?: string,
href: string, // REQUIRED — full URL of the page
landingPage?: string, // derived from href if missing
referrer?: string,
clientIp?: string,
userAgent?: string,
country?: string, // override Vercel geo
city?: string,
region?: string,
fbc?: string, fbp?: string,
gclid?: string, ttclid?: string, sccid?: string,
utmSource?: string, utmMedium?: string, utmCampaign?: string,
utmContent?: string, utmTerm?: string,
}Response:
{
visitId: string,
sessionId: string,
tempId: string,
clientSessionId: string,
cookies: {
tempId: { name: "adtarget_temp_id", value, maxAge: 31536000, sameSite: "Lax" },
session: { name: "adtarget_session_id", value, maxAge: 1800, sameSite: "Lax" }
}
}The cookies block is a hint for proxies: echo them as Set-Cookie headers on the upstream response so the end user keeps the same tempId/sessionId on subsequent requests.
curl
curl -X POST https://adtarget.io/api/v1/events/visit \
-H "Authorization: Bearer atk_live_..." \
-H "Content-Type: application/json" \
-d '{
"href": "https://yoursite.com/landing?fbclid=AbCd",
"fbc": "fb.1.1700000000.AbCd",
"clientIp": "203.0.113.42",
"userAgent": "Mozilla/5.0 ..."
}'POST /api/v1/events/update-fbp
Patch the latest visit’s fbp value. Use when Meta’s fbevents.js sets _fbp after your initial /events/visit call.
Body:
{ tempId: string, fbp: string }Response: { success: boolean }
POST /api/v1/events/invite
Generate a Telegram invite link for a visitor. Returns JSON (no redirect, unlike the tracker’s GET endpoint).
Body:
{
tempId: string,
clientSessionId?: string,
channelId: string // Convex ID or Telegram channel ID (-100...)
}Response: { inviteLink: string }
POST /api/v1/events/conversion
The killer endpoint. Creates a conversion row + dispatches Meta CAPI (and TikTok / Snapchat / Google if configured).
Body:
{
telegramUserId: number,
channelId: string, // Convex ID or Telegram -100... ID
eventType: "Lead" | "Purchase" | "CompleteRegistration" | "Subscribe" | "Custom",
customEventName?: string, // required when eventType === "Custom"
value?: number,
currency?: string, // ISO 4217 (USD, EUR, ...)
contentName?: string,
pii?: {
email?: string,
phone?: string,
firstName?: string,
lastName?: string
},
externalUserId?: string,
eventTime?: number, // Unix millis; defaults to now
// Cold-lead attribution overrides
fbc?: string, fbp?: string,
gclid?: string, ttclid?: string, sccid?: string,
clientIp?: string,
userAgent?: string,
eventSourceUrl?: string,
}Response:
{
conversionId: string,
capiStatus: "pending" | "skipped",
coldLead: boolean,
dispatched: boolean, // false when sends=[] (no platform matched attribution)
warning?: string, // present when dispatched=false; explains why
sends: [{ sendId: string, platform: string }]
}dispatched: falsemeans no platform CAPI was scheduled — the request body lacked attribution signals (fbc/fbp/gclid/ttclid/sccid) that match any configured pixel, or no pixel is configured for this site. The conversion is still stored (you’ll see it in the dashboard) but no CAPI event was fired.capiStatusis"skipped"in this case.dispatched: truedoes NOT guarantee the event was accepted by Meta/TikTok/etc. — it means a send was scheduled. If a downstream check (e.g., missinguser_agentfor Meta) rejects the send, the underlyingconversion_sendsrow will be markedskipped. Poll the dashboard or use Axiom logs to confirm final delivery.
Warm vs cold leads
- Warm: AdTarget has a prior conversion for
(siteId, telegramUserId, channelId). The new conversion inherits itssessionId/visitIdand reuses the original attribution. - Cold: no prior conversion. The supplied
fbc/fbp/clientIp/userAgentare written toconversion.apiVisitOverrideand used for CAPI.
For cold leads, always pass fbc, fbp, clientIp, userAgent, eventSourceUrl to keep Meta’s Event Match Quality high.
POST /api/v1/leads/{telegramUserId}/events
Fire an additional event on an existing lead. Equivalent to POST /events/conversion but auto-resolves channelId from the lead’s most recent conversion.
Body: same as /events/conversion minus telegramUserId and (optionally) channelId.
POST /api/v1/leads/{telegramUserId}/conversions/{conversionId}/value
Submit value + optional PII for a conversion that’s awaitingManualInput (Purchase event with no auto-fire). This is the API equivalent of clicking “Submit value” in the dashboard.
Body:
{
value: number, // > 0
currency: string, // ISO 4217 whitelist
pii?: {
email?, phone?, firstName?, lastName?
}
}Response: { success: boolean }
GET /api/v1/leads/{telegramUserId}
Read everything we know about a lead on this site. Useful for debugging.
Response:
{
telegramUserId: number,
firstSeenAt: number,
lastSeenAt: number,
externalUserId?: string,
conversions: [{
conversionId: string,
channelId: string,
eventType: string,
capiStatus: string,
attributedAt: number,
eventValue?: number,
eventCurrency?: string,
source?: "tracker" | "webhook" | "api"
}]
}Returns 404 LEAD_NOT_FOUND if no conversion exists for the lead on this site.
GET /api/v1/channels
List the active channels for this site, with both the internal Convex ID and the Telegram channel ID. Useful when you only know one and need the other.
Response:
{
channels: [{
channelId: string, // Convex ID
telegramChannelId: string, // "-100..."
title: string,
username?: string,
funnelOrder: number,
eventType?: string
}]
}