Skip to content
Start free
Menu
Server API

Server API

Confirm a conversion from your backend with one HTTP request authenticated by your project's server key. No server SDK is needed.

Browser evidence can be lost: a tab closes at the moment of payment, an extension blocks the request, a network drops. Confirming the conversion from your backend covers that gap. Browser and server evidence of one outcome count as one conversion, because both carry the same stable identifier.

Send the conversion from your server with an HTTP request and your project's server key. There is no server SDK to install.

The endpoint

https://api.tracelog.io/v1/server/events

POST, Content-Type: application/json, and your project's server key in the Authorization header as a bearer token:

HTTP
Authorization: Bearer tl_sk_xxxxxxxxxxxxxxxxxxxxxxxxxx

The server key is a secret. It is not bound to an origin allowlist, anyone holding it can write events to your project, and it must never reach a browser bundle. Rotate it from project settings; the value is shown once, at creation or rotation.

The envelope

JSON
{
  "v": 1,
  "events": [
    {
      "eventId": "019b9ae7-2c00-7000-8000-000000000001",
      "sessionId": "019b9ae7-2c00-7000-8000-000000000002",
      "kind": "conversion",
      "name": "purchase",
      "occurredAt": "2026-01-08T00:00:00.000Z",
      "identifier": "wc-1042",
      "value": 129.5,
      "currency": "EUR"
    }
  ]
}

FieldTypeNotes
v1The envelope version. A version the API does not know is rejected as unknown_version rather than guessed at.
eventIdUUID v7The idempotency key. Redelivering the same eventId never double-counts, so a retry after a timeout is always safe.
sessionIdUUID v7The visit the conversion belongs to. Use the one TraceLog's capture code created in the browser when you have it; otherwise create one.
kindstringconversion, step, session_start, or error.
namestringThe name in the tracking plan: lowercase, starting with a letter, then letters, digits or underscores, up to 64 characters.
occurredAtISO 8601When it happened, in UTC with millisecond precision. TraceLog assigns the event to this time, not to when it arrives.
identifierstringRequired on a conversion, 1 to 256 characters. Your own stable identifier.
valuenumberOptional, zero or greater.
currencystringOptional, ISO 4217 as three uppercase letters.
modestringOptional. verification marks the event as verification traffic, left out of metrics and session limits. Send it when your own test mode produced the order.
contextobjectOptional. Any JSON-serialisable object, up to 8 KB serialised.

A conversion event needs six fields: eventId, sessionId, kind, name, occurredAt and identifier. Limits: at most 50 events per batch and 256 KB per request. An event more than five minutes in the future is rejected; one more than seven days old is accepted and flagged as late.

The response

JSON
{ "accepted": 1, "rejected": [] }

accepted counts the events stored by that request. One invalid event rejects the whole batch: nothing is stored, and rejected lists each eventId that could be read with the batch's reason — unknown_version, invalid_schema or future_time. An eventId or sessionId that is not a UUID v7 is invalid_schema.

An event from more than 30 days back, a day TraceLog no longer keeps, is expired: it is refused on its own and the rest of the batch is stored.

A request over 256 KB or 50 events is refused whole with HTTP 413 and no receipt: that is too_large. Too many requests with one key get HTTP 429 and a Retry-After header, in seconds.

Examples

Command
curl -X POST "$TRACELOG_API/v1/server/events" \
  -H "Authorization: Bearer $TRACELOG_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "v": 1,
    "events": [{
      "eventId": "019b9ae7-2c00-7000-8000-000000000001",
      "sessionId": "019b9ae7-2c00-7000-8000-000000000002",
      "kind": "conversion",
      "name": "purchase",
      "occurredAt": "2026-01-08T00:00:00.000Z",
      "identifier": "1042",
      "value": 129.5,
      "currency": "EUR"
    }]
  }'

JavaScript
await fetch(`${process.env.TRACELOG_API}/v1/server/events`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRACELOG_SERVER_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    v: 1,
    events: [
      {
        eventId: order.eventId, // a UUID v7 minted once per order and stored with it
        sessionId: order.sessionId,
        kind: "conversion",
        name: "purchase",
        occurredAt: new Date().toISOString(),
        identifier: order.number,
        value: order.total,
        currency: order.currency,
      },
    ],
  }),
});

PHP
<?php
$response = wp_remote_post( $api . '/v1/server/events', array(
    'headers' => array(
        'Authorization' => 'Bearer ' . $server_key,
        'Content-Type'  => 'application/json',
    ),
    'body' => wp_json_encode( array(
        'v'      => 1,
        'events' => array( array(
            'eventId'    => $order->get_meta( '_tracelog_event_id' ), // UUID v7 stored with the order
            'sessionId'  => $session_id,
            'kind'       => 'conversion',
            'name'       => 'purchase',
            'occurredAt' => gmdate( 'Y-m-d\TH:i:s.000\Z' ),
            'identifier' => (string) $order->get_order_number(),
            'value'      => (float) $order->get_total(),
            'currency'   => $order->get_currency(),
        ) ),
    ) ),
) );

Python
import json, os, urllib.request

request = urllib.request.Request(
    f"{os.environ['TRACELOG_API']}/v1/server/events",
    method="POST",
    headers={
        "Authorization": f"Bearer {os.environ['TRACELOG_SERVER_KEY']}",
        "Content-Type": "application/json",
    },
    data=json.dumps({
        "v": 1,
        "events": [{
            "eventId": order.tracelog_event_id,  # UUID v7 stored with the order
            "sessionId": session_id,
            "kind": "conversion",
            "name": "purchase",
            "occurredAt": occurred_at,
            "identifier": str(order.number),
            "value": float(order.total),
            "currency": order.currency,
        }],
    }).encode(),
)
urllib.request.urlopen(request)

Retrying

Retry on a network error, a 5xx, or a 429 once its Retry-After has passed, with the same eventId. The eventId is the idempotency key, so a duplicate delivery is stored once and counted once. A new eventId on a retry would be counted again.