Server events
Record backend-confirmed actions and connect them to the originating visitor session.
Choose the right event
- checkout_clicked
- Record in the browser when the visitor clicks the checkout link.
- begin_checkout
- Record on your server after the payment provider creates a checkout session, before redirecting. This does not confirm that the hosted payment page loaded.
- Payment successful
- Verify the provider webhook, then send a purchase through the revenue endpoint.
These custom event names are conventions you choose. Tracwell does not create them automatically. Use a distinct event for each stage to avoid counting a browser click and backend result as the same action.
Authentication and identity
Open Settings → Website → Server key for an active Product-mode website. Use the same write-only server key used for revenue collection. Keep it in a server environment variable; public project keys and MCP read keys cannot authenticate this endpoint.
Send Authorization: Bearer YOUR_SERVER_KEY and Content-Type: application/json to POST https://collect.tracwell.app/v1/server/events. The key determines the project; do not include a project ID or public key in the body.
Read analytics.getSession() in the browser and pass its anonymousId and sessionId with your business request. Both are required. Preserve them in checkout metadata for later payment attribution. An optional opaque user_id does not replace them.
Send a custom event
This example runs after your business action succeeds. Supply the originating page context, not the server URL or server device details. No browser SDK runs on the server.
// Server only. Pass the browser's anonymousId and sessionId
// with the business request, after handling consent on your site.
const batch = {
batch_version: 1,
batch_id: crypto.randomUUID(),
sent_at: new Date().toISOString(),
sdk: { name: "tracwell-server", version: "0.1.0" },
events: [{
schema_version: 1,
event_id: crypto.randomUUID(),
event_type: "event",
event_name: "begin_checkout",
timestamp: new Date().toISOString(),
anonymous_id: anonymousId,
session_id: sessionId,
context: {
url: "https://example.com/pricing",
path: "/pricing"
},
properties: { plan: "pro", provider: "dodo" }
}]
};
// Create this batch once per action. Retry this same serialized body
// and event_id; persist it if retries must survive a process restart.
const body = JSON.stringify(batch);
const response = await fetch(
"https://collect.tracwell.app/v1/server/events",
{
method: "POST",
headers: {
authorization: `Bearer ${process.env.TRACWELL_SERVER_KEY}`,
"content-type": "application/json"
},
body,
signal: AbortSignal.timeout(5000)
}
);
const result = await response.json();
if (response.status !== 202 || result.receipt_version !== 1 ||
result.batch_id !== batch.batch_id ||
result.accepted_events !== batch.events.length) {
throw new Error("Tracwell collection was not acknowledged");
}- Batch
- Version 1; UUID batch ID; ISO timestamps with milliseconds; server SDK name and semantic version; 1–50 events; at most 48 KiB.
- Event
- UUID event ID; event_type must be event; required name, timestamp, anonymous_id, session_id and context; at most 16 KiB.
- Context
- Required absolute URL and slash-prefixed path. Title, referrer and the five standard UTM properties are optional.
- Timestamps
- At most 24 hours old or 5 minutes in the future, including sent_at.
- Reserved
- identify, page_view and revenue event names; properties beginning with $revenue_. Use the revenue endpoint for purchases and refunds.
Custom properties are optional and follow the custom event naming and property limits. Duplicate event IDs within one batch are rejected.
Acceptance and retries
{
"receipt_version": 1,
"receipt_id": "0190f3c6-7a10-7cc2-8b48-8e1acb5f8c01",
"batch_id": "0190f3c6-7a10-7cc2-8b48-8e1acb5f8b01",
"accepted_events": 1,
"accepted_at": "2026-09-05T12:00:00.000Z"
}A matching receipt confirms Queue acceptance, not yet database persistence. Reuse the original event IDs, timestamps and payload on retries. Repeated delivery is deduplicated by the ingestion/reporting path.
Await the request before returning from a short-lived server function. Use bounded backoff for network failures or retryable responses. Durable retry across restarts requires storing the pending event in your application; an in-memory retry loop cannot provide that guarantee.
- 400
- Invalid JSON, schema, timestamp, event size, or reserved revenue fields. Correct the payload.
- 401
- Missing or unavailable server key, including a website without active Product-mode collection access.
- 405 / 415
- Use POST with Content-Type: application/json.
- 413
- Request exceeds the byte limit. Reduce the batch size.
- 429
- Read error.retryable: retryable rate limits may be retried; an exhausted event allowance is not retryable.
- 500 / 503
- Temporary collection failure. Retry the same batch with bounded backoff.