Putting events on your own site and app
From the business question to the line of code on the page: which events you need, where in the code to call them, and how to be sure they arrived.
This page assumes you have a site or an app that works and that nothing is coming out of it yet. Designing events says what our code does with what you send, and the event dictionary gives you a ready-made list per industry. This page does a third thing: it starts from the question you want answered and ends at the line of code that goes on your page.
First, the thing most people expect and that does not exist: Segmentic has no event registration. There is no form, no endpoint and no screen for creating an event. Any name you send is accepted, and the event comes into existence the moment it first arrives. What you are actually doing is choosing a name and its properties, then placing one call in the right spot in your own code.
That freedom cuts both ways. The good side is that adding a new event needs nobody's permission. The bad side is that nothing stops a wrong name, and a wrong name is not corrected later. That is why this page starts with a list rather than with code.
Start from the question, not from the page
The first temptation is to walk through your own screens and send everything that can be clicked. What comes out is eighty names, none of which is attached to a decision.
Do the opposite. Write down three to five questions you would act on if you had the answer. Then, for each one, ask which event answers it.
| The question you are asking | What you would do with the answer | The event you need |
|---|---|---|
| Who abandoned their cart | Send them a reminder | cart_updated and order_completed |
| Which homepage banner works | Replace the weak one | banner_viewed and banner_clicked |
| Who looked at the expensive item and did not buy | Send a targeted discount | product_viewed with price |
| Who has not come back in thirty days | Build a win-back campaign | Any event at all, the last-seen date is enough |
That last row is there on purpose. Some questions need no new event. Before adding a name, check whether what you already send answers it.
Close the list on paper first
Before opening an editor, fill in a table with these four columns. It is what you hand to a developer, and it stays your own reference afterwards.
| Event name | When it fires | Properties | Who sends it |
|---|---|---|---|
banner_viewed | When the banner actually enters the viewport | banner_id, slot | Browser |
banner_clicked | A click on the banner | banner_id, slot, destination | Browser |
product_viewed | A product page opens | product_id, category, price | Browser |
checkout_started | Arrival at the payment screen | cart_value, item_count | Browser |
order_completed | Payment confirmed | order_id, revenue, currency | Server |
The last column matters most and is the one usually left blank. Its answer is in what the browser must not be trusted to say.
Write the "when it fires" column as a verb, not as a screen name. "When the banner enters the viewport" is implementable. "On the homepage" is not, because it does not say where the moment of sending is.
How many events is enough
Five to fifteen, to start.
There is no technical ceiling and nobody is counting. The constraint is elsewhere: the panel's event catalogue shows the five hundred highest-volume names from the last ninety days. Let useless names pile up and your real events fall off that list, which means they are no longer pickable in the segment builder. This happened to a real customer, with names shaped like URLs; the account is in when the event name is a URL.
The second constraint is human. An event that no segment, no report and no journey uses is one that, six months from now, nobody understands and nobody dares delete.
Start small. Adding an event is possible at any moment and costs nothing. Taking a wrong name back out of history is not possible at all.
Naming your own events
The server checks exactly two things: that the name is no longer than 128 bytes and that it holds no control characters. It does not lower-case, does not convert spaces to underscores, and enforces no pattern. The full rule is in the naming rule.
So everything below is a convention rather than a constraint. It is the convention the platform's own standard names follow, though, and staying with it is what keeps your list legible.
| Rule | Write | Do not write |
|---|---|---|
| Object first, then a past-tense verb | banner_clicked | click_banner |
| Lower case and underscores | wallet_topped_up | WalletToppedUp |
| No variable inside the name | banner_clicked with banner_id | banner_nowruz_hero_clicked |
| One name per thing that happened | product_viewed | product_view and productViewed side by side |
Rows two and four share a trap: because the server does not fold case, Banner_Clicked and banner_clicked are two separate events forever. Nothing errors. One day you simply notice the numbers have halved.
Row three is the expensive one. A name carrying an id or any varying value mints a fresh event per value, and that is exactly what fills the catalogue.
One name, several places
Your banner appears on the homepage, at the top of a category page, and inside an email. Do not create three names.
Use one name and put the location in a property:
Segmentic.track("banner_clicked", {
banner_id: "nowruz_hero",
slot: "homepage_top"
});
Your questions want both shapes. "How many clicks did this banner get in total" is answered by the one name, and "which slot performs better" by splitting on the property. Three names leave the first question with no simple answer.
The general rule: what you want to add up is the name, and what you want to break down by is a property.
Where in the code to call it
This is where instrumentation goes wrong. An event at the wrong moment is worse than no event, because it produces a number and the number is false.
| What happened | Where to call it | Why not elsewhere |
|---|---|---|
| A click on a link or button | One listener on document using closest | Banners are usually rendered later or inside a carousel, so a direct listener never reaches them |
| A form submission | After the server answers successfully | On submit you also send the event for forms that were rejected |
| A section becoming visible | With an IntersectionObserver | On page load counts every visitor who never scrolled that far |
| A route change in a single-page app | The SDK itself, via autoPageView | A manual call in useEffect fires again on every re-render |
| A successful payment | From your server | The browser does not know whether the money actually settled |
The next worry is usually that the visitor clicks a link, the page navigates away, and the event never gets sent. That one is already handled: the web SDK flushes every twenty messages or every ten seconds, and on top of that sends with sendBeacon on pagehide and when the page is hidden, which survives unload. The detail is in the offline queue.
A worked example: a banner click
Say the question is: which homepage banner works, and who clicked it.
Install the SDK first. Take the write key from the Connect screen in the panel:
<script src="https://in.segmentic.net/sdk/segmentic.js"></script>
<script>
Segmentic.init({
writeKey: "wk_...",
apiHost: "https://in.segmentic.net"
});
</script>
Then mark the banners themselves. Putting the id in the HTML means the next banner needs no JavaScript change at all:
<a href="/campaign/nowruz"
data-banner="nowruz_hero"
data-slot="homepage_top">
<img src="/banners/nowruz.jpg" alt="Nowruz sale">
</a>
Then one listener, once, for every banner on the site:
document.addEventListener("click", function (e) {
var el = e.target.closest("[data-banner]");
if (!el) return;
Segmentic.track("banner_clicked", {
banner_id: el.dataset.banner,
slot: el.dataset.slot,
destination: el.getAttribute("href")
});
});
If you want a click-through rate, send the impression too. Firing once per element matters, otherwise every scroll past the banner counts again:
var seen = new WeakSet();
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting || seen.has(entry.target)) return;
seen.add(entry.target);
var el = entry.target;
Segmentic.track("banner_viewed", {
banner_id: el.dataset.banner,
slot: el.dataset.slot
});
});
}, { threshold: 0.5 });
document.querySelectorAll("[data-banner]").forEach(function (el) {
io.observe(el);
});
That is all of it. No registration, no migration, no panel setting. banner_clicked exists from the first click.
Inside a mobile app
Do not change the names. The same banner_clicked the web sends is the one the app should send. Do not invent banner_clicked_android, or every question gets asked twice and every report grows a second column.
The SDK puts the platform in context and reports can split on it, so there is no reason to repeat it in the name or in a property.
Installation and methods are in the Android SDK. Where the call goes follows the same logic as above: at the moment of real interaction, not in onCreate and not in a view's constructor.
What the browser must not be trusted to say
The write key is public by design. It sits in your page, anyone can read it, and anyone can send events with it. For banner_clicked that does not matter. For a number that lands in a revenue report it does.
So send these from your own backend rather than from the browser:
- Anything carrying an amount, especially
order_completedand itsrevenue - Anything that is a system of record: shipped, refunded, subscription renewed
- Anything the browser never learns, such as a payment gateway callback that arrives at your server
There are two doors for this and the differences are laid out in server to server. In short: POST /v1/batch on https://in.segmentic.net with the same write key, or POST /v1/events on https://api.segmentic.net with a secret sk_seg_ key.
The second door differs in two silent ways: it does not de-duplicate, so a plain retry in your code creates the event twice, and its window is a fixed thirty days. Do not migrate history through it.
If you send the same event from both the browser and the server, you get two. Pick one. For anything involving money, always the server.
Tying the event to a person
Everything sent so far carries only an anonymous_id. You know a browser clicked the banner. You do not know who, and you cannot email them.
identify is what connects the two. Call it wherever you know who the visitor is: after sign-in, after registration, or on any page where a valid session exists.
Segmentic.identify("u_8842", {
email: "ali@example.com",
phone: "09121234567"
});
Worth knowing: identify does not have to happen before the click. On the first identify, the SDK also sends an alias, so that browser's anonymous history joins the profile. Without that, every funnel crossing the sign-in boundary would report the wrong number.
Call Segmentic.reset() on sign-out, or the next person on that device accumulates onto the previous person's profile. The harder cases, such as a shared device, are in identity.
Verify each one actually arrived
Do not skip this. The event you forgot to check is usually the one that turns out, three months later, never to have arrived at all.
Step one, immediately: leave the Debug screen open in the panel, click the banner in another tab, and watch it appear within a few seconds. If it does not, the problem is the installation and not the event name. One caveat: batched sends are not recorded on that screen, so test with single calls.
Step two, a few minutes later: open the Data screen. The event should be there with its volume and its property list. If the name is there but a property you expected is not, that property was null or empty.
Step three, while you are there: give the event a display label. "Banner click" reads better than banner_clicked for whoever builds the segment. The label is presentation only and the stored name never changes.
The ingest_warnings table exists but nothing writes to it. Warnings live only in the response body of that one request, so if you send from a server, log that response. The ways of seeing what landed are in seeing what actually arrived.
Checklist before going live
- Every event on your list has been seen once on the Debug screen.
- No name contains a variable, and they all follow one casing convention.
- Ids are strings and numbers are numbers, not numeric strings. The rule is in what a property may hold.
revenueappears only on genuinely monetary events. On any event at all it accrues to lifetime value, including oncart_viewed. See revenue.identifyis called wherever the visitor is known, andreseton sign-out.- Monetary events come from the server, not the browser.
- No event is sent twice, once from the browser and once from the server.
- Every event has a display label on the Data screen.
Common mistakes
| Mistake | What breaks | Instead |
|---|---|---|
| A name containing an id or a URL | The catalogue fills and real events drop off the five-hundred list | Make the id a property |
The event on form submit | Rejected submissions get counted | After a successful response |
revenue on a non-monetary event | Lifetime value quietly inflates | Only on a purchase |
Sending url and referrer as properties | Consumes property slots and adds nothing | The SDK already sends them in context |
| Localised values for categories | Filters break across spellings | One language for values, put the label in the panel |
identify only on the sign-in page | A returning visitor with a live session stays anonymous | Wherever the session is valid |
No reset on sign-out | Two people accumulate onto one profile | reset in the sign-out path |
| The same event from browser and server | Every number doubles | Pick one |
What to read next
- Designing events for the exact rules on names, properties and limits.
- The event dictionary if you run retail, fintech, travel or education and want a ready-made list.
- Identity if you have anonymous visitors who later sign in.
- The web SDK for every method and option.
- Server to server for choosing between the two doors.
- Segments once the events arrive and you want to build on them.
- The product catalogue if you run a shop and want to recommend products in your messages.