← Back to Help Center

What webhooks do

A Trakr webhook is an outbound HTTP POST. Whenever a tracking record changes, Trakr sends a small JSON note to a URL you registered. A record is one set of UTM parameters, with or without a landing URL: exactly what the builder, the API, and the MCP connector all produce. You do not poll Trakr for changes; Trakr pushes them to you.

Capture happens at the database level, which is the important part. A record created in the app, one written through the API, and one edited over the MCP connector all raise the same event. There is no surface you can change a record from that the webhook misses. Three things happen to a record, and each one sends a note:

Trakr is the sender in this relationship. You bring the receiver: any endpoint that accepts an HTTP POST works, including an n8n webhook URL, a small serverless function, or a route in your own service.

Setting up a webhook

Open Settings, go to the Webhooks section, and register the URL that should receive the notes. Trakr posts to that URL from the moment the webhook is enabled.

The Webhooks section in Trakr Settings: an endpoint URL field, an endpoints list, a recent-deliveries log, and a signature-verification snippet.
The Webhooks section in Settings: add your endpoint URL, watch the delivery log, and copy the signature-verification snippet.
  1. Paste your receiving URL. If you run automations in n8n, a Webhook node gives you a URL you can drop straight in.
  2. On create, Trakr shows a signing secret once. Copy it into your secrets store immediately: it is how you prove each note is genuine, and it is never shown again.
  3. Save. New changes to your records now arrive at your endpoint as they happen.

The webhook reports changes from when it is enabled, not your back catalogue. Records that already exist do not replay. Seed your first copy with one bulk read (see below), then let the webhook keep it current.

What each note looks like

Every note is a JSON object with a stable envelope:

{
  "id": "evt_7c9e6679",
  "delivery_id": "dlv_5f0a12b8",
  "type": "record.created",
  "created_at": "2026-07-21T10:15:00.000Z",
  "data": { ... }
}
FieldMeaning
idThe event id. Stable and unique per change. Use it, or delivery_id, to discard a repeat.
delivery_idThe id of this delivery attempt. Because delivery is at-least-once, the same event can arrive twice with the same event id; dedupe on it.
typeOne of record.created, record.updated, or record.deleted.
created_atISO timestamp of the change.
dataThe payload. Its shape depends on type, described next.

For record.created and record.updated, data is the full current record: the same object GET /v1/links/:id returns, with id, full_url, the UTM values (utm_source, utm_medium, utm_campaign, and the rest), campaign_id, custom_params, and field_values. Empty fields are left out, so check for key presence rather than comparing against an empty value.

{
  "type": "record.updated",
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "full_url": "https://example.com/summer?utm_source=facebook&utm_medium=cpc&utm_campaign=nl.summer26&utm_id=nl-summer26-social",
    "utm_source": "facebook",
    "utm_medium": "cpc",
    "utm_campaign": "nl.summer26",
    "campaign_id": "nl-summer26-social",
    "field_values": { "productName": "aj", "country": "nl" }
  }
}

For record.deleted, data is minimal: just the id and a deleted flag, so your consumer can remove that record by id without needing the rest.

{
  "type": "record.deleted",
  "data": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "deleted": true }
}

Because created and updated notes carry the whole record every time, and never a partial diff, your consumer can treat both the same way: upsert by id. A note you missed, or one that arrives twice, corrects itself on the next write, so a single stale or duplicated note never leaves your copy wrong for long.

Verifying the note came from Trakr

Your endpoint is a public URL, so treat every request as untrusted until you have checked its signature. Every POST carries four headers:

HeaderWhat it carries
X-Trakr-SignatureThe signature: sha256= followed by the hex HMAC of the timestamp and the body.
X-Trakr-TimestampWhen Trakr signed the request. Part of the signed value, and your recency check.
X-Trakr-EventThe event type, mirroring type in the body, so you can route without parsing first.
X-Trakr-DeliveryThe delivery id, mirroring delivery_id, handy for logging and dedupe.

To verify, compute the expected signature yourself and compare it to the one you received:

timestamp = header "X-Trakr-Timestamp"
rawBody   = the exact request body bytes, unparsed
expected  = "sha256=" + HMAC_SHA256(signing_secret, timestamp + "." + rawBody)
accept if  expected equals header "X-Trakr-Signature"

Two rules make this safe. First, sign the raw body, before any JSON parsing reformats it; a re-serialized body produces a different HMAC and will never match. Second, reject a request whose X-Trakr-Timestamp is not recent, so a captured note cannot be replayed against you later. Use a constant-time comparison for the signature itself.

Delivery, retries, and the log

Trakr aims for at-least-once delivery. Your endpoint should answer quickly with a 2xx status; anything else, or a timeout, counts as a failure and is retried.

Seeding your first copy

A webhook is a stream of changes, not a snapshot. When you first turn one on, your consumer has nothing yet, because only records that change from that point forward are sent. Fill the gap once:

  1. Bulk load the current state. Page through GET /v1/links with your API key and store every record you get back. This is your starting copy.
  2. Enable the webhook. From here on, each create, edit, and delete arrives as a note and keeps your copy fresh.

Do the bulk load and the enable close together, and let upsert-by-id absorb the small overlap: a record that appears in both the initial load and an early note simply gets written twice with the same result. The full read API, including the filters and pagination on GET /v1/links, is documented on the Trakr API page.

Included in Pro

Webhooks are part of the Pro plan, at no extra charge, next to the full API and the MCP connector. There is nothing to request and no per-delivery pricing. If your endpoint has genuinely high volume needs, write to support@trakr.studio and we will help you size it.

Common questions

Are webhooks a Pro feature?

Yes. Webhooks are included in the Pro plan at no extra charge, alongside the API and the MCP connector. You register your own receiving URL in the Webhooks section of Settings, and Trakr starts sending as soon as it is enabled.

What triggers a webhook?

Any change to a tracking record: creating one, editing one, or deleting one. Capture happens at the database level, so a change made in the app, through the API, or over the MCP connector all send the same note. You get one POST per change.

How do I verify a note really came from Trakr?

Every POST carries an X-Trakr-Signature header. Compute an HMAC-SHA256 over your timestamp and the raw request body using your signing secret, then compare it to the signature. Reject anything that does not match, and reject a timestamp that is not recent. The signing secret is shown once, when you create the webhook.

What is in the note for a deleted record?

A delete note carries only the record id and a deleted flag set to true, so your consumer can remove that record by id. Created and updated notes carry the full current record instead.

What happens if my endpoint is down?

Trakr retries with backoff about six times over roughly 24 hours. Delivery is at-least-once, so keep your handler idempotent by tracking the delivery id. An endpoint that keeps failing is paused automatically, and the delivery log in Settings shows the status of every attempt.

How do I load the records that already exist?

A webhook only reports changes made after you turn it on. Do one initial load with GET /v1/links to pull your current records, then let the webhook keep your copy fresh from there.

Open the App, free, no signup →