Webhooks
Webhooks move conversions in and out of TrueMetriks. Take them in from any tool, and send yours back out to any URL with attribution attached. Here is how both work.
Set this up with AI
Copy this prompt into ChatGPT, Claude or whichever AI you use. It carries this whole guide, the pages it depends on, and a link to every video, so it can walk you through it one step at a time.
Webhooks move conversions in and out of TrueMetriks. Open Settings > Integrations > Webhooks and you get two tabs: Receive webhooks brings conversions in, Send webhooks pushes them back out with attribution attached.
Receive webhooks
Bring conversions in from any tool - Zapier, a WordPress form, GoHighLevel, or your own backend. Each webhook gets its own secret URL. Paste that URL into the tool and it sends Leads, booked calls, Purchases or custom events into this site's analytics.
Step 1 - Create a receiving webhook
On the Receive webhooks tab, click Add receiving webhook.
Receive webhooks 3 webhooks
Get conversions INTO TrueMetriks. Each webhook gets its own secret URL - paste it into any tool that can send webhooks.
Webhook URL - paste this into the sending tool
Also fire to connected ad platforms
Sends matching events to Facebook / TikTok / GA4 / Google Ads exactly like a native integration. Analytics reporting always happens either way.
Name it after the tool that will send to it, then pick the event type:
- Lead, Schedule, or Purchase for a standard conversion.
- Custom event name for your own event (for example
ApplicationSubmitted). Custom events show in analytics and can trigger sending webhooks.
You cannot change the event type later, because tools send to this URL expecting it. If you need a different type, create a second webhook. Click Create webhook.
Add receiving webhook
Name
e.g. Zapier leadsA label just for you - name it after the tool that will send to it.
Event type
LeadLead, Schedule, Purchase, or a custom event name. Fixed after creation.
Also fire to connected ad platforms
Sends matching events to Facebook / TikTok / GA4 / Google Ads exactly like a native integration. Analytics reporting always happens either way.
Step 2 - Copy the URL into your tool
Copy the webhook URL and paste it into the tool that will send to it. That is the whole setup. The URL is the only credential, there are no API keys.
Step 3 - Send a payload
POST JSON to the URL (form-encoded works too). We read the common field names, and we find them inside nested payloads too, so most tools work with no setup at all:
curl -X POST 'https://q.your-domain/api/webhooks/in/YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","first_name":"Jane","value":49.99,"currency":"USD"}'
The fields we read are email, phone, first_name, last_name, value, currency and order_id. Anything else you send is kept as event properties. If it works you get HTTP 200 back, with a summary of what we found.
The event then shows in your analytics like any other integration. It only gets credited to an ad if we can tell which visitor it came from, and that part is worth getting right, so it has its own section next.
Make sure the conversion lands on the right visitor
When a conversion arrives by webhook, we have to work out which visitor it came from. If we cannot, the sale still counts and the money is still yours, but it lands on a person with no history, so no ad gets the credit.
Most of the time this takes care of itself. Nearly every tool sends us an email, and nearly every buyer types their email somewhere on your site, so the two line up and the conversion joins the right journey. Nothing for you to do. Capturing the email as early as you have it makes this even more reliable - see window.tmIdentify on the Custom install guide.
When email is not enough
Two things break the email match, and both are worth knowing about:
- They never typed their email on your site. They browsed, then paid somewhere you do not control, so no visit of theirs carries that address. Nothing to match against.
- They typed it two different ways. One spelling at a booking, another at checkout, and to us that is two different people.
We do repair the obvious misspellings, but only so far. We fix a short, fixed list of provider typos, one way only: an email arriving as gmial.com is also tried as gmail.com. An email that arrives spelled correctly is never checked against misspellings, because that could merge two different people. And a typo before the @ (davd@ for david@) is not fixed at all.
So if your conversions keep landing with no visit attached, email alone is not reaching those buyers, and the next section is how you fix it.
The fix - send us the visitor id
This is optional, and most sites never need it. But when email is not landing, it is the one thing that always works, because there is nothing to match and nothing to guess. Everything else is us working out who someone probably is. This is us being told.
Every visitor we track carries a cookie on your own domain called _evid. Read it in the browser, drop it into a hidden field on your form or checkout, and send it in the payload alongside the email you already send.
You can name the field any of these: evid, _evid, tm_evid, visitor_id, tm_visitor_id, tm_id.
Add the hidden field to your form:
<input type="hidden" name="tm_evid" value="">
Then fill it from the cookie at the moment the form is submitted:
<script>
document.addEventListener("submit", function (e) {
var field = e.target.querySelector('input[name="tm_evid"]');
if (!field) return;
var match = document.cookie.match(/(?:^|;\s*)_evid=([^;]*)/);
field.value = match ? decodeURIComponent(match[1]) : "";
}, true);
</script>
Your payload then carries one extra line:
{
"email": "[email protected]",
"value": 49.99,
"currency": "USD",
"tm_evid": "ev_k3f9q2_a1b2c3d4"
}
Read the cookie in the browser. Never bake it into the page. If a CDN caches the page holding your hidden field, one visitor's _evid gets saved into the HTML and handed to everybody who loads it after them, and all of their conversions pile onto that one person. We trust the visitor id above everything else, so nothing later catches the mistake. The snippet above is safe because it reads the cookie as the form is sent, in that visitor's own browser.
Send the click id if you have it
fbclid and gclid sit in the landing-page URL, so you can put them in a hidden field the same way, with no cookie to read. Another option when the visitor id is awkward to get at.
You can name the field any of these: fbclid, gclid, click_id, tm_click_id.
What we try, in order
- Visitor id - exact, no guessing.
- Click id -
fbclidorgclid, matched against the click ids we stored for that visitor. - Email - matched against the email attached to their visits.
We stop at the first one that finds a real visit. For most sites the email on its own gets there, which is why nothing above is required. Sending more than one is safe either way - they do not clash, the strongest one wins.
Why a conversion shows no visit
A conversion with no visit attached means nothing in that payload matched a visitor we had seen. It still counts and the money is still yours, but with no journey behind it there is no ad to credit.
Adding the visitor id fixes it from then on. It cannot go back and fix conversions that already arrived, so the sooner it is in, the sooner your attribution fills in.
Also fire to connected ad platforms
Each receiving webhook has an Also fire to connected ad platforms toggle, off by default. Turn it on and the events also go to Facebook, TikTok, GA4 and Google Ads, just like a native integration. Your analytics get them either way. Custom events work too: Call Booked reaches your ad platforms under that same name.
Send a custom event to Facebook as a Custom Conversion
Once a custom-named receiving webhook exists, its event name shows up in Facebook's Create custom event dialog, under a Custom webhook events group. Pick it there and we create a Facebook Custom Conversion with that name, so Facebook can optimize and report on it. We store it as an OTHER-type conversion, so it is never mistaken for a Lead or a Purchase, and it fires once per event with no duplicates. Facebook only gets the event while that webhook's Also fire to connected ad platforms toggle is on. See the Facebook integration guide for the dialog.
Duplicate protection
We count each conversion once, even when two tools report it:
- Leads, Schedules and custom events: the same email and event type counts once per day, across every source. A webhook and a native integration (say Typeform) reporting the same lead will not double-count.
- Purchases are checked on email, order and amount instead, so two genuinely different orders on the same day both count.
If a native integration already reports the same event type for this site, the webhook shows an amber warning as a reminder.
Recent deliveries
Every receiving webhook keeps a Recent deliveries log: the last 25 receipts, 5 per page. Each one shows what happened (accepted, deduped, parse error, oversized or ignored) plus a preview of the payload, so you can see exactly what arrived.
Rotating or removing a webhook
There is no regenerate button, on purpose. To change a URL, delete the webhook and create a new one, which gets a new URL. Deleting one stops its URL working straight away. Payloads over 256 KB are rejected.
Send webhooks
Push conversions out of TrueMetriks to any URL - a CRM, GoHighLevel, or your own backend. Every Lead, Schedule and Purchase goes to your URL with the person's first and last touch, sessions and paid channel attached. Leads reach GoHighLevel with the whole journey on them.
Step 1 - Create a sending webhook
Switch to the Send webhooks tab and click Add sending webhook.
Send webhooks 2 webhooks, 1 paused
Push conversions OUT of TrueMetriks. Every event is sent to your URL enriched with the person's first and last touch, sessions and paid channel.
Webhook URL - we send events here
Fill in the dialog:
- Name - a label for you (for example "GoHighLevel contacts").
- Webhook URL - where we POST the payload. Must be
https. - Send this event - pick ONE event from the dropdown: Lead, Schedule, Purchase, or one of your custom events (your Facebook Custom Conversions and custom-named receiving webhooks).
One sending webhook sends one event. To send another event to the same place, create a second one.
Click Create webhook.
Add sending webhook
Name
e.g. GoHighLevel contactsWebhook URL
https://your-tool.com/webhookMust be https. We POST a JSON payload to this URL for every matching event.
Send this event
LeadLead, Schedule, Purchase, or one of your custom events (your Facebook Custom Conversions and custom-named receiving webhooks).
Step 2 - Copy the signing secret
We show a signing secret once, right after you create the webhook. Copy it now, you will not see it again.
Sending webhook created
Every webhook we send is signed with this secret so your receiver can verify it came from TrueMetriks. Signature verification is optional.
Signing secret
⚠ Copy it now - you will not see this secret again.
Every delivery is signed so your receiver can check it came from us. The signature rides in an X-TrueMetriks-Signature: t=<unix>,v1=<hmac-sha256> header, built from the request body and your signing secret. Checking it is optional - receivers that do not check signatures (GoHighLevel inbound webhook triggers, for example) can ignore the header.
Step 3 - Know the timing
A delivery fires about 75 seconds after the event arrives. The wait is on purpose: identity and attribution settle first, so the payload carries the full journey. To test the wiring without waiting, click Send test and a sample payload arrives in about 15 to 30 seconds.
The test uses this webhook's real event name (Call Booked, not a generic Test), so your receiver sees exactly what a live delivery looks like. It is still marked with "test": true at the top level (and event.properties.test: true) so nothing downstream counts it as a real conversion, and in the Recent deliveries log the row reads Test: Call Booked. On a test send contact is null and attribution is sample data.
The payload
Every delivery is a JSON POST. event.type is Lead, Schedule or Purchase for standard events, and Custom for your own, so your receiver can branch on it:
{
"api_version": "2026-07-09",
"webhook": { "destination_id": "wdst_...", "attempt": 1, "sent_at": "2026-07-11T10:17:27.718Z" },
"site": { "id": 1, "domain": "example.com" },
"event": { "type": "Purchase", "name": "Purchase", "id": "evt_...", "time": "...", "value": 49.99, "currency": "USD", "source": "webhook", "properties": {} },
"contact": { "email": "[email protected]", "phone": null, "first_name": "Jane", "last_name": null },
"attribution": {
"first_touch": { "source": "google", "medium": "cpc", "campaign": "brand-search", "channel": "Paid Search", "referrer": "https://www.google.com/", "landing_url": "example.com/landing" },
"last_touch": { "source": "facebook", "medium": "paid", "campaign": "retargeting", "channel": "Paid Social", "referrer": "https://facebook.com/", "landing_url": "example.com/offer" },
"touches": 7, "sessions": 3, "days_to_convert": 7, "paid": true, "paid_channel": "Paid Social"
}
}
On real events contact carries the person's actual email, phone, and name, and attribution their real journey. On test sends contact is null and attribution is sample data.
A custom event delivers the same shape, with event.type set to Custom and event.name set to your event name:
{
"api_version": "2026-07-09",
"webhook": { "destination_id": "wdst_...", "attempt": 1, "sent_at": "2026-07-17T10:17:27.718Z" },
"site": { "id": 1, "domain": "example.com" },
"event": { "type": "Custom", "name": "Call Booked", "id": "evt_...", "time": "...", "source": "webhook", "properties": {} },
"contact": { "email": "[email protected]", "phone": null, "first_name": "Jane", "last_name": null },
"attribution": {
"first_touch": { "source": "google", "medium": "cpc", "campaign": "brand-search", "channel": "Paid Search" },
"last_touch": { "source": "facebook", "medium": "paid", "campaign": "retargeting", "channel": "Paid Social" },
"touches": 4, "sessions": 2, "days_to_convert": 1, "paid": true, "paid_channel": "Paid Social"
}
}
A test delivery of that same webhook looks identical but adds a top-level "test": true and event.properties.test: true, so your receiver can drop it.
Reliability
- Stable IDs, so you can drop repeats. Each delivery carries an
X-TrueMetriks-Delivery: dlv_<id>header and anevent.idfield, so your receiver can spot a retry. TheX-TrueMetriks-Eventheader carries the event name. - Automatic retries, spaced further apart each time, when a delivery fails.
- Dead URLs pause themselves. A URL that keeps failing is paused for you. The card shows why, with an Unpause button once your receiver is fixed.
- Recent deliveries shows every attempt (
delivered,queued,sendingorfailed) with the HTTP code.
Related
- Send conversions into GoHighLevel: GoHighLevel install guide
- Fire conversions from your own site: Custom install guide
- Capture the email on your site so webhook conversions match: window.tmIdentify
- Forward conversions to your ad platforms: Facebook CAPI, Google Ads, GA4, TikTok
Frequently asked questions
How does TrueMetriks know which visitor a webhook conversion belongs to?
It tries the visitor id first, then a click id, then the email address, and stops at the first one that finds a real visit. For most sites the email is enough, because buyers type it on the page. If conversions keep arriving with no visit attached, add the visitor id from the _evid cookie in a hidden field.
Do I need API keys to receive webhooks?
No. Create a receiving webhook, copy its secret URL, and paste that URL into the sending tool. The URL is the only credential.
What payload format does a receiving webhook accept?
POST JSON (form-encoded also works). TrueMetriks reads a simple schema (email, phone, first_name, last_name, value, currency, order_id) and auto-maps most other or nested payloads by key, so most tools work with no payload configuration.
How does TrueMetriks stop the same conversion counting twice?
The same email and event type within one UTC day counts once, across every source, so a webhook and a native integration reporting the same lead do not double-count. Purchases dedup on email, order, and amount instead, so two genuinely different orders both count.
Can a sending webhook include attribution?
Yes. Every sent payload carries the person's first and last touch, channel, sessions, and days to convert, so a CRM like GoHighLevel receives the full journey with each conversion.
How do I verify a sent webhook came from TrueMetriks?
Each delivery is signed with an X-TrueMetriks-Signature header (t=
Can one sending webhook send more than one event?
No. A sending webhook sends exactly one event - Lead, Schedule, Purchase, or one of your custom events. To send another event to the same place, create a second sending webhook.
When does a sending webhook fire?
About 75 seconds after the event is ingested, so identity and attribution settle first. Send test delivers a sample payload within about 15 to 30 seconds.