An event dictionary for each kind of business
Ready-made event and property lists for online retail, super-apps, fintech, travel, education and subscription media. Copy and start.
This page is written to be copied. Six kinds of business, and for each one a list of events with their properties that you can take as it stands and start with. The reasoning behind the choices is in designing events; if you have not read it, read at least revenue, because that is the one place where a property name changes a number on a person's profile. And to get this list actually running on your own site or app, see placing events. And if you want to recommend products in your messages, the id you put in product_id here has to match the product catalogue.
How to read this
The "status" column in every table has two values and the difference matters:
- Standard means the platform declares this name and the panel gives it a Persian label. That is the whole of it.
- Proposed means the name is our convention. The platform does not know it and treats it like any other name. You may change it; just change it before the first send.
One common misreading, closed here: "standard" does not mean "in the event picker". Until the tenant's real schema loads, the segment builder shows ten fixed names (order_completed, product_viewed, product_added_to_cart, checkout_started, order_refunded, searched, app_opened, signed_up, message_opened, message_clicked) and after that it builds the list from your own events. The journey builder labels only eight. A standard name you have never sent appears in no list at all.
Three things to keep separate about the standard list:
Only three names actually have behaviour. order_completed adds the amount to the profile's total_revenue, increments order_count and moves last_order_at. order_refunded and order_cancelled subtract the absolute amount and decrement the order count, both floored at zero. The rest of the standard names have no behaviour at all.
Standard means recognised, not automatic. No event is sent by itself. The names app_installed, app_opened, app_updated, app_removed, session_started and session_ended are declared in the code as "lifecycle events the SDKs emit automatically", and no SDK emits them. If you want them, send them.
**Do not send the message_* names yourself.** Segmentic's own workers write them, and they are listed under what Segmentic writes.
The names the platform knows
| Name | Panel label | What the code does |
|---|---|---|
order_completed | خرید | raises the profile's revenue and order count, moves last_order_at, and is the orders figure in the daily rollup |
order_refunded | مرجوعی | lowers revenue and order count, floored at zero |
order_cancelled | لغو سفارش | the same as order_refunded |
product_viewed | مشاهده محصول | nothing. A label |
product_added_to_cart | افزودن به سبد | nothing |
product_removed_from_cart | حذف از سبد | nothing |
cart_viewed | مشاهده سبد | nothing |
checkout_started | شروع پرداخت | nothing |
searched | جستجو | nothing |
signed_up | ثبتنام | nothing |
signed_in | ورود | nothing |
app_opened | باز کردن اپ | nothing, and no SDK sends it |
page_viewed | مشاهده صفحه | the default name of a page message with no name |
screen_viewed | none | the default name of a screen message with no name |
The properties you put on every event
Before the tables, five rules that are the same across all six businesses.
Do not resend the context. context already carries device, OS, app version, page, campaign and timezone, and the collector puts each in its own column. A platform property on every event is a worse copy of device_type.
Send identifiers as strings. "order_id": "A-100294", not "order_id": 100294. A number is stored as text too, but a string joins against your own system from the start.
Send numbers as numbers and booleans as booleans. "quantity": 3, not "quantity": "3". A numeric-looking string never reaches the numeric map, and a "greater than" filter on it never answers.
Pick one language for property values and stay with it. Values are stored as sent, with Persian normalisation only. gold and طلایی are two values and do not land in the same segment.
Use revenue, total, value, and the pair price with quantity, only on an event where money moved. Revenue is extracted from any event carrying those keys and added to that person's lifetime value. For an amount that is not real money, use another name. The tables below deliberately use unit_price, cart_total and amount, none of which is reserved.
Online retail
| Event name | Status | When to send it | Properties |
|---|---|---|---|
product_viewed | standard | a product page opened | product_id string, product_name string, category string, brand string, unit_price number, currency string, in_stock boolean |
searched | standard | a search was submitted | query string, results_count number, sort string, filters string |
product_added_to_cart | standard | added to the basket | product_id string, product_name string, unit_price number, quantity number, cart_size number |
product_removed_from_cart | standard | removed from the basket | product_id string, quantity number, cart_size number |
cart_viewed | standard | the basket page opened | cart_size number, cart_total number, currency string |
checkout_started | standard | the payment step was entered | cart_size number, cart_total number, currency string, shipping_method string, coupon_code string |
order_completed | standard | payment succeeded | order_id string, revenue number, currency string, item_count number, shipping_cost number, discount number, coupon_code string, payment_method string |
order_refunded | standard | a refund was approved | order_id string, revenue number meaning the amount returned, currency string, reason string |
order_cancelled | standard | the order was cancelled before dispatch | order_id string, revenue number, currency string, cancelled_by string |
signed_up | standard | registration completed | method string, referral_code string |
signed_in | standard | a successful sign-in | method string |
order_shipped | proposed | the parcel was handed to the carrier | order_id string, carrier string, hours_since_order number |
order_delivered | proposed | the parcel reached the customer | order_id string, carrier string, days_since_order number |
review_submitted | proposed | a review was posted | product_id string, rating number, has_photo boolean |
wishlist_item_added | proposed | added to the wish list | product_id string, unit_price number |
back_in_stock_requested | proposed | asked to be told when it returns | product_id string |
The default funnel this list supports: product_viewed, then product_added_to_cart, then checkout_started, then order_completed. The abandoned-basket segment is the same list: anyone with a product_added_to_cart in the last seven days and no order_completed.
curl -X POST https://in.segmentic.net/v1/track \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wk_seg_..." \
-d '{
"message_id": "b3d51f28-4c6a-4b1e-9d70-2f8a3c5e7d10",
"user_id": "u_88123",
"event": "order_completed",
"timestamp": "2026-08-07T10:02:41.000Z",
"properties": {
"order_id": "A-100294",
"revenue": 2450000,
"currency": "IRR",
"item_count": 3,
"shipping_cost": 49000,
"discount": 150000,
"coupon_code": "NOWRUZ",
"payment_method": "gateway"
}
}'
{"status":"ok","accepted":1}
Super-app and ride hailing
| Event name | Status | When to send it | Properties |
|---|---|---|---|
app_opened | standard | every time the app comes to the foreground. You have to send it | is_first_open boolean, source string |
service_selected | proposed | the user opened one of the super-app's services | service string |
ride_requested | proposed | a ride was requested | ride_id string, service_class string, origin_city string, destination_city string, estimated_fare number, surge_multiplier number |
ride_accepted | proposed | a driver accepted | ride_id string, wait_seconds number |
ride_cancelled | proposed | the ride ended before completion | ride_id string, cancelled_by string, seconds_to_cancel number |
ride_completed | proposed | the ride finished and the fare was settled | ride_id string, revenue number, currency string, distance_km number, duration_minutes number, payment_method string |
ride_rated | proposed | a rating was submitted | ride_id string, rating number |
order_completed | standard | a food or grocery order was paid for | order_id string, revenue number, currency string, vendor_id string, item_count number, service string |
wallet_topped_up | proposed | the wallet was topped up | revenue number, currency string, method string |
promo_applied | proposed | a promotion code was applied | coupon_code string, discount number, service string |
support_ticket_opened | proposed | a support ticket was opened | topic string, service string, ride_id string |
Using revenue on wallet_topped_up and ride_completed is deliberate. The code explicitly allows for this: revenue accrues from any event, not only orders, because a customer may model a subscription or a top-up as its own event. Note though that only order_completed increments order_count, so the profile's order count does not count rides.
Put service on every super-app event. Without it you cannot ask for "took a taxi but never ordered food".
Fintech and payments
| Event name | Status | When to send it | Properties |
|---|---|---|---|
signed_up | standard | registration completed | method string, referral_code string |
kyc_started | proposed | the user entered the verification flow | level string, hours_since_signup number |
kyc_submitted | proposed | documents were uploaded | level string, document_type string |
kyc_approved | proposed | verification passed | level string, hours_to_approve number |
kyc_rejected | proposed | verification was refused | level string, reason string |
card_linked | proposed | a bank card was attached | bank string, card_type string |
transfer_completed | proposed | money was transferred | transfer_id string, amount number, currency string, destination_type string |
bill_paid | proposed | a bill was paid | bill_type string, amount number, currency string |
payment_completed | proposed | a payment at a merchant went through | revenue number meaning your fee, currency string, merchant_id string, category string, amount number |
loan_application_started | proposed | a credit application began | amount number, term_months number |
loan_approved | proposed | credit was granted | amount number, term_months number, days_to_decision number |
investment_order_placed | proposed | a buy or sell order was placed | asset string, side string, amount number |
amount appears here instead of revenue on purpose. The value of a transfer is not your money: call it revenue and every user's total_revenue becomes their account turnover, so the "high-value customer" segment becomes "moves a lot of money around". On payment_completed both appear, because you want both figures: revenue is the fee that is yours and amount is what the user paid.
Do not put card numbers, IBANs, cvv values or payment tokens in properties. Event properties are shown verbatim to your support team on the user timeline and they appear in the CSV export. If you need it, put the last four digits in a separate property such as card_last4.
Travel and booking
| Event name | Status | When to send it | Properties |
|---|---|---|---|
searched | standard | a route or stay search was submitted | origin string, destination string, depart_date string, return_date string, passengers number, results_count number |
product_viewed | standard | a flight, hotel or tour was opened | product_id string, product_type string, unit_price number, currency string, star_rating number |
checkout_started | standard | the booking flow began | cart_total number, currency string, passengers number, product_type string |
order_completed | standard | the booking was confirmed and paid for | order_id string, revenue number, currency string, product_type string, origin string, destination string, depart_date string, days_to_departure number |
order_cancelled | standard | the booking was cancelled | order_id string, revenue number, refund_amount number, days_to_departure number |
trip_started | proposed | the departure day arrived | order_id string, product_type string, destination string |
trip_completed | proposed | the trip ended | order_id string, product_type string, nights number |
price_alert_created | proposed | a price alert was set | origin string, destination string, target_price number |
booking_modified | proposed | dates or passengers changed | order_id string, change_type string, fee number |
days_to_departure is stored on the event rather than computed at query time. The difference is that months later you can still ask "how many people booked within three days of the flight", whereas with only depart_date you would have to subtract two strings at report time, and a date string in the property map is text.
Education
| Event name | Status | When to send it | Properties |
|---|---|---|---|
signed_up | standard | registration completed | method string, referral_code string |
course_viewed | proposed | a course page opened | course_id string, course_name string, category string, unit_price number, is_free boolean |
course_enrolled | proposed | enrolment completed | course_id string, is_free boolean |
order_completed | standard | a course or subscription was bought | order_id string, revenue number, currency string, course_id string |
lesson_started | proposed | a lesson was opened | course_id string, lesson_id string, lesson_index number |
lesson_completed | proposed | a lesson was watched to the end | course_id string, lesson_id string, lesson_index number, watch_seconds number |
quiz_submitted | proposed | a quiz was submitted | course_id string, quiz_id string, score number, passed boolean |
assignment_submitted | proposed | an assignment was handed in | course_id string, assignment_id string, days_late number |
certificate_issued | proposed | a certificate was issued | course_id string, days_to_complete number |
course_abandoned | proposed | no lesson for a while and you have decided that counts as abandoned | course_id string, last_lesson_index number, days_idle number |
course_abandoned has to be decided by your own system and sent from your server; the platform does not manufacture an event out of the absence of one. If you would rather not build it, do the same thing with a journey that waits after lesson_completed.
Do not put course progress on the profile as a trait unless a user only ever has one course. A trait is one value per person, so with two courses the second erases the first.
Subscription media
| Event name | Status | When to send it | Properties |
|---|---|---|---|
signed_up | standard | an account was created | method string |
content_viewed | proposed | playback or reading started | content_id string, content_type string, title string, genre string, is_premium boolean |
content_completed | proposed | watched or read to the end | content_id string, watch_seconds number, completion_ratio number |
paywall_viewed | proposed | the paywall was shown | content_id string, plan_shown string |
trial_started | proposed | a trial began | plan string, trial_days number |
subscription_started | proposed | the subscription went live and was charged | plan string, revenue number, currency string, billing_period string |
subscription_renewed | proposed | an automatic renewal succeeded | plan string, revenue number, currency string, renewal_number number |
subscription_cancelled | proposed | the user cancelled | plan string, reason string, days_subscribed number |
payment_failed | proposed | a renewal failed | plan string, attempt number, failure_code string |
download_started | proposed | downloaded for offline viewing | content_id string, content_type string |
The thing that always surprises people in subscriptions: subscription_cancelled returns no money. Only order_refunded and order_cancelled subtract from total_revenue. A cancellation means the next renewal does not happen, not that previous months are refunded, and the profile says exactly that.
The traits you send with identify
These are the keys the code gives meaning to, and which become real profile columns:
| Key | Type | Note |
|---|---|---|
email | string | lower-cased only, with no format validation |
phone | string | parsed and stored as E.164, plus a derived phone_operator |
first_name and last_name | string | as sent |
gender | string | mapped to male, female or other |
birthday | date | the formats 2006-01-02, 2006/01/02 and RFC3339 are accepted; a malformed date is silently dropped |
national_id | string | the check digit is verified; an invalid one is dropped entirely |
city, region, country | string | filled from the event's location if you do not send them |
language | string | filled from the first part of locale if you do not send it |
timezone | string | filled from the event's context.timezone if you do not send it |
push_opt_in, email_opt_in, sms_opt_in | boolean | the true values are true, 1, yes, on and «بله». Everything else is false |
A new profile starts with all three consents on. An existing customer base has already consented through your own flow, and defaulting to off would make your first campaign reach nobody.
name and created_at are also declared as reserved keys, but they are not promoted to columns and stay ordinary traits. If you want a full name, send first_name and last_name.
Every other key is a free-form trait and lands in the traits and traits_num maps. A shop usually also sends: loyalty_tier string, loyalty_points number, is_wholesale boolean, preferred_category string.
curl -X POST https://in.segmentic.net/v1/identify \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wk_seg_..." \
-d '{
"message_id": "c74a9e01-5f22-4a8b-8c31-7ee2d9b40a55",
"user_id": "u_88123",
"traits": {
"phone": "09123456789",
"email": "Hamid@Example.com",
"first_name": "حمید",
"city": "تهران",
"birthday": "1993-04-11",
"sms_opt_in": true,
"loyalty_tier": "gold",
"loyalty_points": 1840
}
}'
{"status":"ok","accepted":1}
What that does to the profile: phone becomes +989123456789 and phone_operator is written as mci, email becomes hamid@example.com, loyalty_points lands in both the text and the numeric map so that a "greater than" filter works, and loyalty_tier stays text only.
What Segmentic writes
These four names are written into your event stream by the platform's own workers. Do not send them yourself, but do build segments and funnels on them like any other event.
| Name | When it is written | Properties |
|---|---|---|
message_sent | a campaign message was sent successfully | channel, category, transport, node_id, arm which is treatment or control, can_open, can_click. Plus why_open and why_click when a signal cannot be measured, which are ready-to-print Persian sentences |
message_failed | the send failed | the same set, plus reason |
message_withheld | the user was chosen for the campaign and deliberately not messaged | the same set, with arm as control and reason naming the hold |
message_opened | the open-tracking endpoint was called | none. The campaign's message id lands in source_message_id, and the device and location come from the request the mail client made |
The message_id of these events combines the request id and the event name, so that a send and a failure for the same message do not de-duplicate against each other.
message_delivered, message_bounced and unsubscribed are declared as constants and nothing else in the backend uses them: no worker writes them and nothing reads them, so a report built on message_delivered comes back empty. The panel asks for a delivery event anyway. Its messaging dashboard template and the starter metrics for delivery rate, open rate and click rate each need one mapped to a name from your own event schema, and activation does not proceed until you name one, so unless you send a delivery event yourself, that dashboard and those three metrics are never built.
What is missing is the event, not the data. A campaign report counts its own delivered messages from the receipts a transport sends back, the deliverability service reads bounces off the SMTP return path and writes them to the suppression list, and an unsubscribe lands on the person's record in the preference centre and comes back as the suppression reason on the next send. None of that arrives as a row in your event stream.
The attribution worker recognises exactly four names, message_sent, message_withheld, message_opened and message_clicked, and skips every other event it is handed. message_clicked is not written by a worker either: the web SDK raises it when a visitor lands on a link carrying sg_mid, once, until a different message id replaces it. The comparison is against the one id in storage, not against a history of them, so a visitor who lands on one message, then a second, then the first one again reports the first click twice.
Next: identity explains how these events attach to one person, and designing events lists the decisions that cannot be undone.