Event reference
Send the names exactly as written. Property names are snake_case throughout. Anything not marked required is optional, and extra properties of your own are fine — they are stored alongside the rest.
You do not need all thirty. Most stores send five: Product Viewed, Product Added, Checkout Started, Order Completed and Products Searched.
The product object
Several events carry a products array. Every entry uses the same shape:
{
product_id: "P-4471", // required
sku: "WH-BLK-01",
name: "Wireless Headphones",
price: 199.99,
quantity: 1,
currency: "SAR",
category: "Electronics",
brand: "AudioTech",
variant: "Black",
position: 3,
coupon: "SUMMER20",
url: "https://store.example.com/p/headphones",
image_url: "https://store.example.com/i/headphones.jpg"
}
price and quantity are worth filling in even where they are optional: when an order arrives without a total, Flowfy rebuilds revenue by summing price × quantity across the array.
Money
| Property | Meaning |
|---|---|
value | What the order is worth. This is the revenue field |
currency | Three-letter code — SAR, USD |
subtotal | Before shipping, tax and discount |
discount | Amount taken off |
shipping | Shipping charged |
tax | Tax charged |
Send value on anything carrying money; revenue is accepted as a synonym. Always send currency beside it — an amount with no currency cannot be compared against ad spend.
Browsing
| Event | Required | Also useful |
|---|---|---|
Products Searched | query | results_count, products |
Product List Viewed | products, and one of list_id / list_name / category | — |
Product List Filtered | filters | list_id, list_name, category, sorts, products |
Product Viewed | product_id | every product field |
Product Clicked | product_id | product fields, list_id, list_name |
filters is an array of { type, value }. sorts is an array of { field, direction }, where direction is asc or desc.
flowfyanalytics.track("Product Viewed", {
product_id: "P-4471",
name: "Wireless Headphones",
price: 199.99,
currency: "SAR",
category: "Electronics"
});
Promotions
| Event | Required | Also useful |
|---|---|---|
Promotion Viewed | promotion_id, name | creative, placement, position |
Promotion Clicked | promotion_id, name | creative, placement, position |
Cart and checkout
| Event | Required | Also useful |
|---|---|---|
Product Added | product_id, quantity | product fields, cart_id, source |
Product Removed | product_id, quantity | product fields, cart_id |
Cart Viewed | cart_id, products | currency, value |
Checkout Started | checkout_id, currency, value, products | subtotal, discount, shipping, tax, cart_id, coupon |
Checkout Step Viewed | checkout_id, step | step_name, payment_method, shipping_method |
Checkout Step Completed | checkout_id, step | step_name, payment_method, shipping_method |
Payment Info Entered | checkout_id, payment_method | order_id, shipping_method, step |
step is a whole number starting at 1. source on Product Added says where it came from: product_page, wishlist, search, listing, recommendation or other.
flowfyanalytics.track("Checkout Started", {
checkout_id: "CHK-8821",
currency: "SAR",
value: 523.17,
subtotal: 489.98,
shipping: 22.00,
tax: 11.19,
products: [
{ product_id: "P-4471", name: "Wireless Headphones", price: 199.99, quantity: 1 },
{ product_id: "P-2210", name: "Phone Case", price: 145.00, quantity: 2 }
]
});
Orders
Four events follow an order from checkout to its outcome. Order Completed opens the order; the other three move it on. Flowfy keeps each order's latest state and uses it to take cancelled, refunded and returned orders out of your revenue.
| Event | Sets the order's state to |
|---|---|
Order Completed | placed |
Order Updated | whatever you send in status |
Order Cancelled | cancelled |
Order Refunded | refunded |
Order Completed is the one event worth getting exactly right — it is what every advertising platform optimises against. The other three are never forwarded to advertising platforms; they only correct Flowfy's own numbers.
order_id is what makes an order one order. Without it Flowfy cannot tell a repeated event from a second purchase, so duplicates are counted twice, and a later update has nothing to attach to. Send the same order_id on all four events. transaction_id is accepted in its place. An order_id of 0 is treated as missing.
Spell it Order Cancelled, with two Ls. Order Canceled, with one, is accepted too and does the same thing. For these four names, case, spaces, underscores and hyphens are also ignored, so OrderCompleted, order_cancelled and Order-Refunded are recognised — but send the names as written here.
The seven states
| State | Meaning |
|---|---|
placed | The order exists |
confirmed | Accepted and being prepared |
shipped | With the carrier, or out for delivery |
delivered | In the customer's hands |
cancelled | Called off, before or after shipping |
refunded | Money went back to the customer, in full or in part |
returned | The goods came back |
Order Completed
| Property | Required | Notes |
|---|---|---|
order_id | Yes | Your order number |
currency | Recommended | Three-letter code |
value | Recommended | What the order is worth. revenue is accepted as a synonym |
products | Yes | Every line in the order. These lines become the order's contents |
total | No | The grand total, if it differs from value. Order outcomes use it first |
subtotal, discount, shipping, tax | No | The money breakdown |
payment_method | No | See the payment methods below |
order_placed_at | No | When the order was created, if you send this event later. If it is missing or unreadable, the event's timestamp is taken as the order time |
checkout_id, coupon, shipping_method | No | — |
{
"type": "track",
"event": "Order Completed",
"userId": "customer_9931",
"messageId": "8f14e45f-ea0a-4c1f-9f2c-3b7d1a0e5c62",
"timestamp": "2026-09-18T14:02:11.000Z",
"properties": {
"order_id": "ORD-12345",
"currency": "SAR",
"value": 523.17,
"subtotal": 489.98,
"shipping": 22.00,
"tax": 11.19,
"coupon": "SUMMER20",
"payment_method": "mada",
"products": [
{ "product_id": "P-4471", "name": "Wireless Headphones", "price": 199.99, "quantity": 1 },
{ "product_id": "P-2210", "name": "Phone Case", "price": 145.00, "quantity": 2 }
]
}
}
Order Updated
Send it whenever the order moves forward — confirmed, shipped, delivered, returned.
| Property | Required | Notes |
|---|---|---|
order_id | Yes | The same order_id as the original Order Completed |
status | Yes | The order's new state. currentStatus is read if status is missing |
order_placed_at | Strongly recommended | When the order was originally created. See "When the order was placed" below |
currency, value, total, subtotal, discount, shipping, tax | No | Send them if the amounts changed |
payment_method | No | See the payment methods below |
products | No | — |
previous_status, changed_fields | No | Kept with the event for your own reference |
status takes confirmed, shipped, delivered, cancelled, refunded or returned. It also takes Salla's and Zid's own status codes, which Flowfy maps for you — see the tables below. Upper case and surrounding spaces are ignored. placed changes nothing here: only Order Completed places an order. Any other value — processing, a label in Arabic, a status from another platform — is stored, but the order's state does not change.
The new state goes in status. A current_status property is kept, but it is not read.
{
"type": "track",
"event": "Order Updated",
"userId": "customer_9931",
"messageId": "0b7c2d9e-5a41-4f3e-9d6b-7e2a1c4f8b10",
"timestamp": "2026-09-20T09:15:42.000Z",
"properties": {
"order_id": "ORD-12345",
"status": "shipped",
"order_placed_at": "2026-09-18T14:02:11.000Z",
"previous_status": "confirmed",
"currency": "SAR",
"value": 523.17,
"payment_method": "mada"
}
}
Order Cancelled
| Property | Required | Notes |
|---|---|---|
order_id | Yes | The same order_id as the original Order Completed |
order_placed_at | Strongly recommended | When the order was originally created |
currency, value | Recommended | What the cancelled order was worth |
cancellation_reason | No | Free text |
previous_status, payment_method, products | No | — |
{
"type": "track",
"event": "Order Cancelled",
"userId": "customer_9931",
"messageId": "c3e1f7a2-8d4b-4b6a-a1f0-2e9d5c7b3a41",
"timestamp": "2026-09-19T11:40:03.000Z",
"properties": {
"order_id": "ORD-12345",
"order_placed_at": "2026-09-18T14:02:11.000Z",
"currency": "SAR",
"value": 523.17,
"cancellation_reason": "Customer changed their mind",
"previous_status": "confirmed"
}
}
Order Refunded
| Property | Required | Notes |
|---|---|---|
order_id | Yes | The same order_id as the original Order Completed |
order_placed_at | Strongly recommended | When the order was originally created |
refund_amount, currency | Recommended | The amount actually refunded |
refund_id | No | Your refund reference — useful when one order can be refunded more than once |
reason, products | No | — |
For a partial refund, send only the returned lines and the amount actually refunded. For a full refund the products array can be left out entirely. Flowfy never assumes a refund was for the full amount.
{
"type": "track",
"event": "Order Refunded",
"userId": "customer_9931",
"messageId": "5d2a8f61-3c9e-4e07-b8a4-91f6e0d2c7b5",
"timestamp": "2026-09-27T16:22:48.000Z",
"properties": {
"order_id": "ORD-12345",
"order_placed_at": "2026-09-18T14:02:11.000Z",
"refund_id": "RF-2207",
"refund_amount": 290.00,
"currency": "SAR",
"reason": "Damaged in transit",
"products": [
{ "product_id": "P-2210", "name": "Phone Case", "price": 145.00, "quantity": 2 }
]
}
}
When the order was placed
Every outcome is filed under the day the order was placed, not the day it changed: a cancellation sent today for an order from last week corrects last week's numbers. So Order Updated, Order Cancelled and Order Refunded need the order's original creation time. Flowfy looks for it in this order:
order_placed_aton the event.created_aton the event.createdAton the event.- An
Order Completedfor the sameorder_id, sent in the same batch or earlier.
An order_placed_at Flowfy cannot read counts as missing, so the next step is tried. If none of them is there, the event is stored but not counted in order outcomes. It is never dated by the time it was sent. Send the time in ISO 8601 with a timezone, such as 2026-09-18T14:02:11Z; Unix seconds or milliseconds, even sent as text, are accepted too. Orders placed more than 400 days before the event arrives are not counted, and neither are orders placed more than a day after Flowfy received the event.
Payment methods
payment_method tells Flowfy a cash-on-delivery order from a prepaid one. Upper and lower case are treated the same, and so are spaces, hyphens and underscores — Credit Card, cash-on-delivery and credit_card all work. These values are recognised:
| Kind | Values |
|---|---|
| Cash on delivery | cod, cash_on_delivery, cash on delivery, zid_cod |
| Card | credit_card, card, mada, apple_pay, stc_pay, visa, mastercard |
| Bank transfer | bank_transfer, bank |
| Wallet | wallet, customer_wallet |
| Buy now, pay later | tabby, tamara |
Any other value is kept as you sent it and grouped as other.
Salla and Zid statuses
If your store runs on Salla or Zid and you forward its status changes yourself, you can send the platform's status code as status just as it is — Flowfy maps it for you. You can also send the Flowfy state directly. Use the status code, never the name shown in the dashboard — merchants can rename those.
Salla — the status slug:
| Salla status | Flowfy state |
|---|---|
payment_pending, waiting_for_payment_confirmation, under_review | no state change (the order is placed by Order Completed) |
in_progress, completed | confirmed |
shipped, delivering | shipped |
delivered | delivered |
restoring | delivered — a return has started; the order stays open until it comes back |
canceled, payment_failed | cancelled |
restored | returned |
Salla spells canceled with one L; Flowfy's state has two. For a custom status, send the built-in status it is based on.
Zid — order_status.code:
| Zid status | Flowfy state |
|---|---|
new | no state change (the order is placed by Order Completed) |
preparing, ready | confirmed |
indelivery (inDelivery in webhooks) | shipped |
delivered | delivered |
reverse_in_progress | delivered — a return has started; the order stays open until it comes back |
cancelled, canceled | cancelled |
partially_reversed | refunded |
reversed | returned |
Zid uses both cancelled and canceled; treat them as the same. Zid's payment_status matters too: refunded means refunded — unless status is already cancelled, which wins — and voided means cancelled. pending and paid do not change the state.
Flowfy does not read payment_status, though. For a Zid refund or void, send Order Refunded or Order Cancelled, or send status as refunded or cancelled.
Zid's initial status changes nothing — the order has not been placed yet.
Coupons
| Event | Required |
|---|---|
Coupon Entered | coupon, and one of cart_id / checkout_id / order_id |
Coupon Applied | coupon, and one of cart_id / checkout_id / order_id |
Coupon Denied | coupon, reason, and one of cart_id / checkout_id / order_id |
Coupon Removed | coupon, and one of cart_id / checkout_id / order_id |
coupon is the code the shopper typed. coupon_id, coupon_name and discount are optional.
Wishlist
| Event | Required | Also useful |
|---|---|---|
Product Added to Wishlist | product_id, wishlist_id | product fields, wishlist_name |
Product Removed from Wishlist | product_id, wishlist_id | product fields, wishlist_name |
Note the lower-case to and from in those two names.
When a shopper moves something from a wishlist into the cart, send Product Added with source: "wishlist".
Sharing
| Event | Required | Also useful |
|---|---|---|
Product Shared | product_id, share_via | product fields, recipient, share_message |
Cart Shared | cart_id, products, share_via | recipient, share_message |
WhatsApp Clicked | href | target_phone, source, element_id, page_url, page_title |
share_via is one of email, whats_app, sms, social, link, other. On Cart Shared, each entry in products needs only product_id.
Reviews
| Event | Required | Also useful |
|---|---|---|
Product Reviewed | product_id, review_id, rating | review_body |
rating is a whole number from 1 to 5.
Leads and forms
| Event | Required | Also useful |
|---|---|---|
Lead | lead_source | lead_id, contact_method, form_id, status, estimated_value, currency |
Form Submitted | form_id | form_name, action, status, fields, locale, url |
lead_source is one of contact_form, lead_form, whats_app, phone, live_chat, quote_request, callback_request, newsletter, other.
Do not put a shopper's name, email or phone into Lead properties. Send them through identify instead, where they are hashed before being forwarded.
Which events count as conversions
These are treated as conversions in reporting: Order Completed, Checkout Completed, Purchase, Lead, Sign Up, Form Submitted, Contact, CompleteRegistration.
Of those, only Order Completed, Checkout Completed and Purchase carry revenue. A Lead that happens to include a products array will never have that array summed into your revenue — which is what keeps cost-per-lead honest.