├── track ├── tests │ └── __init__.py ├── version.py ├── const.py ├── utils.py ├── request.py ├── __init__.py ├── consumer.py └── client.py ├── .gitignore ├── setup.cfg ├── Makefile ├── LICENSE ├── setup.py └── README.md /track/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /track/version.py: -------------------------------------------------------------------------------- 1 | VERSION = '1.0.7' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **sublime** 2 | *.pyc 3 | dist 4 | *.egg-info 5 | dist 6 | MANIFEST 7 | build 8 | .eggs 9 | venv -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | license_file = LICENSE 4 | 5 | [bdist_wheel] 6 | universal = 1 7 | -------------------------------------------------------------------------------- /track/const.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | DEFAULT_HOST = 'https://api.interakt.ai' 4 | 5 | 6 | class ApiPaths(enum.Enum): 7 | User = '/v1/public/track/users/' 8 | Event = '/v1/public/track/events/' 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | release: 2 | python3 setup.py clean 3 | python3 setup.py sdist bdist_wheel 4 | twine upload --skip-existing dist/* 5 | 6 | install: 7 | python3 setup.py sdist bdist_wheel 8 | pip3 install -e . 9 | 10 | uninstall: 11 | pip3 uninstall interakt-track-python 12 | 13 | .PHONY: release install uninstall 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 interakt.ai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /track/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from phonenumbers.phonenumberutil import region_code_for_country_code 3 | 4 | logger = logging.getLogger('interakt') 5 | 6 | 7 | def require(name, field, data_type): 8 | """Require that the named `field` has the right `data_type`""" 9 | if not isinstance(field, data_type): 10 | msg = '{0} must have {1}, got: {2}'.format(name, data_type, type(field)) 11 | raise AssertionError(msg) 12 | 13 | 14 | def verify_country_code(country_code: str): 15 | """Verifies country code of the phone number""" 16 | country_code = country_code.replace("+", "") 17 | if not country_code.isdigit(): 18 | raise AssertionError(f"Invalid country_code {country_code}") 19 | 20 | region_code = region_code_for_country_code(int(country_code)) 21 | if region_code == "ZZ": 22 | raise AssertionError(f"Invalid country_code {country_code}") 23 | 24 | 25 | def remove_trailing_slash(host): 26 | if host.endswith('/'): 27 | return host[:-1] 28 | return host 29 | 30 | 31 | def stringify(val): 32 | if val is None: 33 | return None 34 | if isinstance(val, str): 35 | return val 36 | return str(val) 37 | -------------------------------------------------------------------------------- /track/request.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from requests import sessions 4 | from requests.auth import HTTPBasicAuth 5 | 6 | from track.utils import remove_trailing_slash 7 | from track.version import VERSION 8 | from track.const import DEFAULT_HOST 9 | 10 | _session = sessions.session() 11 | logger = logging.getLogger('interakt') 12 | 13 | 14 | def post(api_key, host=None, path=None, body=None, timeout=10): 15 | """Post the msg to the API""" 16 | auth = HTTPBasicAuth(username=api_key, password="") 17 | headers = { 18 | 'Content-Type': 'application/json', 19 | 'User-Agent': f'interakt-track-python/{VERSION}' 20 | } 21 | url = remove_trailing_slash(host or DEFAULT_HOST) + path 22 | logger.debug(f'Making request: {body}') 23 | response = _session.post(url=url, headers=headers, 24 | auth=auth, json=body, timeout=timeout) 25 | if 200 <= response.status_code <= 299: 26 | logger.debug("Data uploaded successfully") 27 | return response 28 | 29 | try: 30 | payload = response.json() 31 | logger.debug(f'Received response: {payload}') 32 | raise APIError(payload.get("result"), 33 | response.status_code, payload.get("message")) 34 | except ValueError: 35 | raise APIError('Unknown', response.status_code, response.text) 36 | 37 | 38 | class APIError(Exception): 39 | 40 | def __init__(self, status, status_code, message): 41 | self.message = message 42 | self.status = status 43 | self.status_code = status_code 44 | 45 | def __str__(self): 46 | msg = "[interakt-track] StatusCode({0}): {1} (Success={2})" 47 | return msg.format(self.status_code, self.message, self.status) 48 | -------------------------------------------------------------------------------- /track/__init__.py: -------------------------------------------------------------------------------- 1 | from track.version import VERSION 2 | from track.client import Client 3 | __version__ = VERSION 4 | 5 | """Settings""" 6 | api_key = None 7 | default_client = None 8 | host = None 9 | sync_mode = False 10 | debug = False 11 | timeout = 10 12 | max_retries = 3 13 | on_error = None 14 | max_queue_size = 10000 15 | 16 | 17 | def user(user_id=None, country_code='+91', phone_number=None, traits={}): 18 | """Send an identify call for customer""" 19 | return _proxy('user', user_id=user_id, country_code=country_code, 20 | phone_number=phone_number, traits=traits) 21 | 22 | 23 | def event(user_id=None, event=None, traits={}, country_code="+91", phone_number=None): 24 | """Send an event track call.""" 25 | return _proxy('event', user_id=user_id, event=event, traits=traits, 26 | country_code=country_code, phone_number=phone_number) 27 | 28 | 29 | def flush(): 30 | """Tell the client to flush.""" 31 | _proxy('flush') 32 | 33 | 34 | def join(): 35 | """Block program until the client clears the queue""" 36 | _proxy('join') 37 | 38 | 39 | def shutdown(): 40 | """Flush all messages and cleanly shutdown the client""" 41 | _proxy('shutdown') 42 | 43 | 44 | def _proxy(method, *args, **kwargs): 45 | """Create an analytics client if one doesn't exist and send to it.""" 46 | global default_client 47 | if not default_client: 48 | default_client = Client( 49 | api_key=api_key, 50 | host=host, 51 | debug=debug, 52 | sync_mode=sync_mode, 53 | timeout=timeout, 54 | max_queue_size=max_queue_size, 55 | on_error=on_error, 56 | max_retries=max_retries 57 | ) 58 | 59 | fn = getattr(default_client, method) 60 | return fn(*args, **kwargs) 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | 5 | try: 6 | from setuptools import setup 7 | except ImportError: 8 | from distutils.core import setup 9 | 10 | # Don't import interakt-track-python module here, since deps may not be installed 11 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'track')) 12 | from track.version import VERSION # noqa 13 | 14 | 15 | # TODO: update long description in README.md 16 | with open(file='README.md', mode='r', encoding='utf-8') as f: 17 | readme = f.read() 18 | 19 | install_requires = [ 20 | "requests>=2.20,<3.0", 21 | "backoff==1.10.0", 22 | "phonenumbers" 23 | ] 24 | 25 | tests_require = [ 26 | "mock==4.0.2", 27 | "pylint==2.6.0", 28 | "flake8==3.8.3", 29 | "coverage==5.2.1" 30 | ] 31 | 32 | 33 | setup( 34 | name='interakt-track-python', 35 | packages=['track', 'track.tests'], 36 | version=VERSION, 37 | url='https://github.com/interakt/track-python', 38 | author="Amar Jaiswal", 39 | author_email="amar.j@cawstudios.com", 40 | maintainer="interakt.ai", 41 | license='MIT License', 42 | description='The easy way to integrate track apis for interakt', 43 | keywords=['INTERAKT', 'KIWI'], 44 | install_requires=install_requires, 45 | extras_require={'test': tests_require}, 46 | long_description=readme, 47 | long_description_content_type='text/markdown', 48 | classifiers=[ 49 | # Chose either "3 - Alpha", "4 - Beta" or "5 - Production/Stable" 50 | 'Development Status :: 4 - Beta', 51 | 'Intended Audience :: Developers', 52 | 'Topic :: Software Development :: Build Tools', 53 | 'License :: OSI Approved :: MIT License', 54 | "Operating System :: OS Independent", 55 | 'Programming Language :: Python :: 3.6', 56 | 'Programming Language :: Python :: 3.7', 57 | 'Programming Language :: Python :: 3.8', 58 | ], 59 | ) 60 | -------------------------------------------------------------------------------- /track/consumer.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from threading import Thread 3 | from track.request import post, APIError 4 | import backoff 5 | 6 | try: 7 | from queue import Empty 8 | except ImportError: 9 | from Queue import Empty 10 | 11 | 12 | class Consumer(Thread): 13 | """Consumes the messages from the client's queue.""" 14 | logger = logging.getLogger('interakt') 15 | 16 | def __init__(self, queue, api_key, host=None, 17 | on_error=None, retries=3, timeout=10, 18 | flush_interval=0.5): 19 | Thread.__init__(self) 20 | self.running = True 21 | self.queue = queue 22 | self.api_key = api_key 23 | self.host = host 24 | self.on_error = on_error 25 | self.retries = retries 26 | self.timeout = timeout 27 | self.flush_interval = flush_interval 28 | 29 | def run(self): 30 | """Runs the consumer.""" 31 | self.logger.debug('consumer is running...') 32 | while self.running: 33 | self.upload() 34 | 35 | self.logger.debug('consumer exited.') 36 | 37 | def pause(self): 38 | """Pause the consumer.""" 39 | self.running = False 40 | 41 | def upload(self): 42 | queue = self.queue 43 | queue_msg = None 44 | try: 45 | queue_msg = queue.get(block=True, timeout=self.flush_interval) 46 | except Empty: 47 | self.logger.debug("queue is empty now") 48 | 49 | if not queue_msg: 50 | self.logger.debug("Nothing left in queue exiting") 51 | return False 52 | try: 53 | self.request(queue_msg=queue_msg) 54 | success = True 55 | except Exception as e: 56 | self.logger.warning(f"Error uploading: {e}") 57 | success = False 58 | if self.on_error: 59 | self.on_error(e, queue_msg) 60 | finally: 61 | self.queue.task_done() 62 | return success 63 | 64 | def request(self, queue_msg): 65 | """Attempt to upload the queue_msg and retry before raising an error """ 66 | 67 | def fatal_exception(exc): 68 | if isinstance(exc, APIError): 69 | # retry on server errors and client errors 70 | # with 429 status code (rate limited), 71 | # don't retry on other client errors 72 | return (400 <= exc.status_code < 500) and exc.status_code != 429 73 | else: 74 | # retry on all other errors (eg. network) 75 | return False 76 | 77 | @backoff.on_exception( 78 | backoff.expo, 79 | Exception, 80 | max_tries=self.retries + 1, 81 | giveup=fatal_exception) 82 | def send_request(): 83 | post(api_key=self.api_key, host=self.host, 84 | path=queue_msg['path'], body=queue_msg['body'], timeout=self.timeout) 85 | 86 | send_request() 87 | -------------------------------------------------------------------------------- /track/client.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import logging 3 | import numbers 4 | 5 | from track.const import ApiPaths 6 | from track.consumer import Consumer 7 | from track.request import post 8 | from track.utils import require, stringify, verify_country_code 9 | 10 | try: 11 | import queue 12 | except ImportError: 13 | import Queue as queue 14 | 15 | ID_TYPES = (numbers.Number, str) 16 | 17 | 18 | class Client(object): 19 | logger = logging.getLogger('interakt') 20 | 21 | def __init__(self, api_key=None, host=None, debug=False, 22 | sync_mode=False, timeout=10, max_queue_size=10000, 23 | on_error=None, max_retries=3, flush_interval=0.5): 24 | """Create a new interakt client""" 25 | require('api_key', api_key, str) 26 | 27 | self.queue = queue.Queue(maxsize=max_queue_size) 28 | self.api_key = api_key 29 | self.debug = debug 30 | self.host = host 31 | self.sync_mode = sync_mode 32 | self.timeout = timeout 33 | self.on_error = on_error 34 | if debug: 35 | self.logger.setLevel(logging.DEBUG) 36 | 37 | if sync_mode: 38 | self.consumer = None 39 | else: 40 | atexit.register(self.join) 41 | self.consumer = Consumer( 42 | queue=self.queue, api_key=api_key, 43 | host=host, on_error=on_error, retries=max_retries, 44 | timeout=timeout, flush_interval=flush_interval 45 | ) 46 | self.consumer.start() 47 | 48 | def user(self, user_id=None, country_code='+91', phone_number=None, traits={}): 49 | """Tie a user to their actions and record traits about them.""" 50 | if not user_id and not phone_number: 51 | raise AssertionError("Either user_id or phone_number is required") 52 | 53 | if user_id: 54 | require('user_id', user_id, ID_TYPES) 55 | 56 | if phone_number: 57 | require('country_code', country_code, str) 58 | verify_country_code(country_code) 59 | require('phone_number', phone_number, str) 60 | if not phone_number.isdigit(): 61 | raise AssertionError(f"Invalid phone_number {phone_number}") 62 | 63 | require('traits', traits, dict) 64 | body = { 65 | 'traits': traits 66 | } 67 | if user_id: 68 | body['userId'] = stringify(val=user_id) 69 | 70 | if phone_number: 71 | body['countryCode'] = country_code 72 | body['phoneNumber'] = phone_number 73 | 74 | return self.__queue_request(path=ApiPaths.User.value, body=body) 75 | 76 | def event(self, user_id=None, event=None, traits={}, country_code="+91", phone_number=None): 77 | """To record user events""" 78 | traits = traits or {} 79 | if not user_id and not phone_number: 80 | raise AssertionError("Either user_id or phone_number is required") 81 | 82 | if user_id: 83 | require('user_id', user_id, ID_TYPES) 84 | 85 | if phone_number: 86 | require('country_code', country_code, str) 87 | verify_country_code(country_code) 88 | require('phone_number', phone_number, str) 89 | if not phone_number.isdigit(): 90 | raise AssertionError(f"Invalid phone_number {phone_number}") 91 | 92 | require('traits', traits, dict) 93 | require('event', event, str) 94 | if not event: 95 | raise AssertionError("event is required") 96 | 97 | body = { 98 | 'event': event, 99 | 'traits': traits 100 | } 101 | if user_id: 102 | body['userId'] = stringify(val=user_id) 103 | 104 | if phone_number: 105 | body['countryCode'] = country_code 106 | body['phoneNumber'] = phone_number 107 | 108 | return self.__queue_request(path=ApiPaths.Event.value, body=body) 109 | 110 | def flush(self): 111 | """Forces a flush from the internal queue to the server""" 112 | queue = self.queue 113 | size = queue.qsize() 114 | queue.join() 115 | # Note that this message may not be precise, because of threading. 116 | self.logger.debug('Successfully flushed about %s items.', size) 117 | 118 | def join(self): 119 | """Ends the consumer thread once the queue is empty. 120 | Blocks execution until finished 121 | """ 122 | self.consumer.pause() 123 | try: 124 | self.consumer.join() 125 | except RuntimeError: 126 | # consumer thread has not started 127 | pass 128 | 129 | def shutdown(self): 130 | """Flush all messages and cleanly shutdown the client""" 131 | self.flush() 132 | self.join() 133 | 134 | def __queue_request(self, path, body): 135 | # Directly call api in sync mode and return response 136 | if self.sync_mode: 137 | return post(self.api_key, host=self.host, path=path, body=body, timeout=self.timeout) 138 | 139 | queue_msg = { 140 | 'path': path, 141 | 'body': body 142 | } 143 | try: 144 | self.queue.put(queue_msg, block=False) 145 | self.logger.debug(f'Enqueued msg for {path}') 146 | return True, queue_msg 147 | except queue.Full: 148 | self.logger.warning('track-python queue is full') 149 | return False, queue_msg 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interakt Track Python 2 | SDK : [interakt-track-python](https://pypi.org/project/interakt-track-python/) 3 | 4 | # Getting Started 5 | 6 | Install `interakt-track-python` using pip 7 | 8 | pip install interakt-track-python 9 | 10 | ## Authentication 11 | Inside your app, you’ll want to set your `api_key` before making any track calls: 12 | 13 | To find your API key, 14 | 15 | - go to your interakt account's Settings --> Developer Settings 16 | 17 | - copy the Secret Key. 18 | 19 | - Make sure you base64 decode this Secret Key (you may choose to do that from [Base64 Decode and Encode- online](https://www.base64decode.org/) ) 20 | 21 | - Erase the ':' at the end of the base64 decoded key 22 | 23 | - Use this key 24 | 25 | ``` 26 | import track 27 | 28 | track.api_key = "YOUR_API_KEY" 29 | ``` 30 | 31 | 32 | ## Development Settings 33 | 34 | The default initialization settings are production-ready and queue messages to be processed by a background thread. 35 | 36 | In development you might want to enable some settings to make it easier to spot problems. Enabling `track.debug` will log debugging info to the Python logger. You can also add an `on_error` handler to specifically print out the response you’re seeing from our API. 37 | ``` 38 | def on_error(error, queue_msg): 39 | print("An error occurred", error) 40 | print("Queue message", queue_msg) 41 | 42 | track.debug = True 43 | track.on_error = on_error 44 | ``` 45 | ### All Settings: 46 | | Settings name | Type | Default value | Description | 47 | | -------------- | -------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ | 48 | | sync_mode | bool | False | When `True`, calls the track API **synchronously**. When `False`, calls the track APIs **asynchronously** using a Queue. | 49 | | debug | bool | False | To turn on debug logging | 50 | | timeout | int | 10 | Timout for track API calls | 51 | | max_retries | int | 3 | Number of API retries in case API call fails due to some error | 52 | | max_queue_size | int | 10000 | Max Queue size | 53 | | on_error | function | None | Callback function which is called whenever an error occurs in **asynchronous** mode | 54 | 55 | 56 | # APIs 57 | ## User 58 | The user track API allows customers to record the attributes specific to the user. They can record specific user properties (attributes) such as user id, email id, phone number, name etc. Customers can call the usertrack API when a new user account is created on their website/app/CRM. For example: 59 | 60 | 61 | **For adding a new user to your interakt dashboard**, the following payload could be sent in the API call: 62 | 63 | ``` 64 | track.user( 65 | user_id="", 66 | country_code="+91", 67 | phone_number="9999999999", 68 | traits={ 69 | "name": "John Doe", 70 | "email": "john@email.com", 71 | "age": 24, 72 | “dob”: “1998-01-01” 73 | } 74 | ) 75 | ``` 76 | 77 | The different user attributes (traits) can be of the following data types: String, Numbers, Boolean, Datetime, List. **Check out the different filter options available in interakt, for different trait types -** [link](https://ik.imagekit.io/z4utvq9kty5/interakt_filters_K5dMwG4qe.pdf). 78 | ‍
79 |
80 | **Note\*\***: Specifying either the **user Id** or **phoneNumber (with countryCode)** is **Mandatory** 81 |
82 | ‍
83 | The above API call records the “userId” or “phoneNumber” (with “countryCode”) as a unique identifier for the user and also adds attributes such as name, email, dob. 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | 95 | 96 | 97 | 98 | 99 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 135 | 136 | 137 |
Event Property Type Status Description 93 |
user_idStringOptional** The userId can take any string value that you specify. This will henceforth be used as a unique identifier for the user. 100 |
101 | The user id parameter will remain the same throughout the lifetime of the customer & cannot be updated. It is recommended that you use your database id instead of a plain username, as database ids generally never change.
phone_numberStringMandatory**phone number of the user without the country code (we recommend that you send the Whatsapp phone number of the user, so that you can send messages to that user via interakt, else the messages won’t get sent) 109 | 110 |
country_codeStringMandatory**Country code of the phone number. The default value is “+91”. 118 | 119 |
traits ObjectOptionalUser attributes such as name, email id etc.
created_atDateOptionalTimestamp of the event in ISO-8601 format date string. If you have not passed the “createdAt” property, we will automatically use the current utc server time as the value of “createdAt” 134 |
138 | 139 | 140 | 141 | **To update attributes for the above user**, the following payload could be sent in the API call: (suppose the “dob” attribute needs to be changed to “1997-12-01”). 142 | ‍ 143 | ``` 144 | track.user( 145 | user_id="", 146 | country_code="+91", 147 | phone_number="9999999999", 148 | traits={ 149 | “dob”: “1997-12-01” 150 | } 151 | ) 152 | ``` 153 | ‍ 154 | **To add a new attribute for the above user**, the following payload could be sent in the API call: (suppose the “pin code” attribute needs to be added). 155 | ‍ 156 | ``` 157 | track.user( 158 | user_id="", 159 | country_code="+91", 160 | phone_number="9999999999", 161 | traits={ 162 | “Pin code”: “400001” 163 | } 164 | ) 165 | ``` 166 | ‍ 167 | ‍**Note**: 168 | 169 | 1. In case, the above user had originally been added via the interakt dashboard (and not by calling the User Track API), then, no userId would exist for that user. In that case, you could either: 170 | - Call the User Track API without specifying a “userId” (and only specifying the “phoneNumber” & “countryCode”), or, 171 | - Include a userId in the API call, which will then get added for that user, and you could use that userId to reference that user in future API calls.* 172 | ‍ 173 | 2. Currently, we don’t provide the option for deleting any user / user attribute. 174 | 175 | **Please make sure the added userId doesn’t belong to an already existing user in your interakt account, else the API will throw an error.* 176 | 177 | 178 | 179 | 180 | 181 | ## Event 182 | The event track API allows customers to record user actions. Each user action (such as a new order created, new user sign up, and so on) will trigger an event to the endpoint. For example: 183 | 184 | **For adding a new event for a particular user**, the following payload could be sent in the API call: 185 | 186 | ```‍ 187 | 188 | track.event( 189 | user_id="", 190 | event="Order Placed", 191 | “traits”={ 192 | “orderCreatedBy”: “Gavin Roberts”, 193 | “orderCreatedDate”: “2020-11-01T012:10:26.122Z”, 194 | “orderNumber”: “CUS001”, 195 | “orderValue”: “50.00” 196 | }, 197 | country_code="+91", 198 | phone_number="9999999999", 199 | ) 200 | ``` 201 | 202 | The above API call triggers an OrderPlaced event when your user makes an order on your website/app. The API call passes the event properties orderCreatedBy, orderCreatedDate, orderNumber and orderValue to the API endpoint. 203 | ‍
204 | **Please note:** In case userId doesn't exist for a user, "phoneNumber" & "countryCode" would need to be specified in the above Event Track API Call. 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 263 | 264 | 265 |
Event Property Type Status Description 214 |
user_idStringOptionalUnique identifier for the user.
eventStringOptionalThe action that the user has performed. It’s important to give meaningful names for your events. Example: OrderCreated, NewSignUp, OrderExecuted, etc. 228 |
traitsObjectOptionalProperties are additional bits of information corresponding to the user action that is performed. They provide detailed information about user actions. 236 |
created_at DateOptionalTimestamp of the event in ISO-8601 format date string. If you have not passed the “createdAt” property, we will automatically use the current utc server time as the value of “createdAt” 244 |
phone_numberStringMandatory**phone number of the user without the country code (we recommend that you send the Whatsapp phone number of the user, so that you can send messages to that user via interakt, else the messages won’t get sent) 252 | 253 |
country_codeStringMandatory**Country code of the phone number. The default value is “+91”. 261 | 262 |
266 | 267 | --------------------------------------------------------------------------------