def get_data(request): data = None try: data = load_data_from_request(request) except RequestParsingError as error: capture_exception( error ) # We still capture this on Sentry to identify actual potential bugs return ( None, cors_response( request, generate_exception_response("capture", f"Malformed request data: {error}", code="invalid_payload"), ), ) if not data: return ( None, cors_response( request, generate_exception_response( "capture", "No data found. Make sure to use a POST request when sending the payload in the body of the request.", code="no_data", ), ), ) return data, None
def get_decide(request: HttpRequest): response = { "config": { "enable_collect_everything": True }, "editorParams": {}, "isAuthenticated": False, "supportedCompression": ["gzip", "lz64"], } if request.COOKIES.get(settings.TOOLBAR_COOKIE_NAME): response["isAuthenticated"] = True if settings.JS_URL: response["editorParams"] = { "jsURL": settings.JS_URL, "toolbarVersion": "toolbar" } if request.user.is_authenticated: r, update_user_token = decide_editor_params(request) response.update(r) if update_user_token: request.user.temporary_token = secrets.token_urlsafe(32) request.user.save() response["featureFlags"] = [] response["sessionRecording"] = False if request.method == "POST": try: data_from_request = load_data_from_request(request) except (json.decoder.JSONDecodeError, TypeError): return cors_response( request, JsonResponse( { "code": "validation", "message": "Malformed request data. Make sure you're sending valid JSON.", }, status=400, ), ) team = get_team_from_token(request, data_from_request) if team: response["featureFlags"] = feature_flags(request, team, data_from_request["data"]) if team.session_recording_opt_in and (on_permitted_domain( team, request) or len(team.app_urls) == 0): response["sessionRecording"] = {"endpoint": "/s"} return cors_response(request, JsonResponse(response))
def get_decide(request: HttpRequest): response = { "config": {"enable_collect_everything": True}, "editorParams": {}, "isAuthenticated": False, "supportedCompression": ["gzip", "lz64"], } if request.COOKIES.get(settings.TOOLBAR_COOKIE_NAME): response["isAuthenticated"] = True if settings.JS_URL: response["editorParams"] = {"jsURL": settings.JS_URL, "toolbarVersion": "toolbar"} if request.user.is_authenticated: team = request.user.team permitted_domains = ["127.0.0.1", "localhost"] for url in team.app_urls: hostname = parse_domain(url) if hostname: permitted_domains.append(hostname) if (parse_domain(request.headers.get("Origin")) in permitted_domains) or ( parse_domain(request.headers.get("Referer")) in permitted_domains ): response["isAuthenticated"] = True editor_params = {} if request.user.toolbar_mode == "toolbar": editor_params["toolbarVersion"] = "toolbar" if settings.JS_URL: editor_params["jsURL"] = settings.JS_URL response["editorParams"] = editor_params if not request.user.temporary_token: request.user.temporary_token = secrets.token_urlsafe(32) request.user.save() response["featureFlags"] = [] if request.method == "POST": feature_flags_data = feature_flags(request) if feature_flags_data["has_malformed_json"]: return cors_response( request, JsonResponse( {"code": "validation", "message": "Malformed request data. Make sure you're sending valid JSON.",}, status=400, ), ) response["featureFlags"] = feature_flags_data["flags_enabled"] return cors_response(request, JsonResponse(response))
def get_decide(request: HttpRequest): response = { "config": { "enable_collect_everything": True }, "editorParams": {}, "isAuthenticated": False, "supportedCompression": ["gzip", "lz64"], } if request.COOKIES.get(settings.TOOLBAR_COOKIE_NAME): response["isAuthenticated"] = True if settings.JS_URL: response["editorParams"] = { "jsURL": settings.JS_URL, "toolbarVersion": "toolbar" } if request.user.is_authenticated: team = request.user.team_set.get() permitted_domains = ["127.0.0.1", "localhost"] for url in team.app_urls: hostname = parse_domain(url) if hostname: permitted_domains.append(hostname) if (parse_domain(request.headers.get("Origin")) in permitted_domains) or (parse_domain( request.headers.get("Referer")) in permitted_domains): response["isAuthenticated"] = True editor_params = {} if request.user.toolbar_mode == "toolbar": editor_params["toolbarVersion"] = "toolbar" if settings.JS_URL: editor_params["jsURL"] = settings.JS_URL response["editorParams"] = editor_params if not request.user.temporary_token: request.user.temporary_token = secrets.token_urlsafe(32) request.user.save() response["featureFlags"] = feature_flags(request) return cors_response(request, JsonResponse(response))
def get_decide(request: HttpRequest): response = { "config": { "enable_collect_everything": True }, "editorParams": {}, "isAuthenticated": False, } if request.user.is_authenticated: team = request.user.team_set.get() permitted_domains = ["127.0.0.1", "localhost"] for url in team.app_urls: hostname = parse_domain(url) if hostname: permitted_domains.append(hostname) if (parse_domain(request.headers.get("Origin")) in permitted_domains) or (parse_domain( request.headers.get("Referer")) in permitted_domains): response["isAuthenticated"] = True editor_params = {} if request.user.toolbar_mode == 'toolbar': editor_params['toolbarVersion'] = 'toolbar' if settings.DEBUG: editor_params["jsURL"] = "http://localhost:8234/" response["editorParams"] = editor_params if not request.user.temporary_token: request.user.temporary_token = secrets.token_urlsafe(32) request.user.save() response["featureFlags"] = feature_flags(request) return cors_response(request, JsonResponse(response))
def get_decide(request: HttpRequest): response = { "config": { "enable_collect_everything": True }, "editorParams": {}, "isAuthenticated": False, "supportedCompression": ["gzip", "gzip-js", "lz64"], } if request.COOKIES.get(settings.TOOLBAR_COOKIE_NAME): response["isAuthenticated"] = True if settings.JS_URL: response["editorParams"] = { "jsURL": settings.JS_URL, "toolbarVersion": "toolbar" } if request.user.is_authenticated: r, update_user_token = decide_editor_params(request) response.update(r) if update_user_token: request.user.temporary_token = secrets.token_urlsafe(32) request.user.save() response["featureFlags"] = [] response["sessionRecording"] = False if request.method == "POST": try: data = load_data_from_request(request) except RequestParsingError as error: capture_exception( error ) # We still capture this on Sentry to identify actual potential bugs return cors_response( request, generate_exception_response(f"Malformed request data: {error}", code="malformed_data"), ) token = _get_token(data, request) team = Team.objects.get_team_from_token(token) if team is None and token: project_id = _get_project_id(data, request) if not project_id: return cors_response( request, generate_exception_response( "Project API key invalid. You can find your project API key in PostHog project settings.", code="invalid_api_key", type="authentication_error", status_code=status.HTTP_401_UNAUTHORIZED, ), ) user = User.objects.get_from_personal_api_key(token) if user is None: return cors_response( request, generate_exception_response( "Invalid Personal API key.", code="invalid_personal_key", type="authentication_error", status_code=status.HTTP_401_UNAUTHORIZED, ), ) team = user.teams.get(id=project_id) if team: response["featureFlags"] = get_active_feature_flags( team, data["distinct_id"]) if team.session_recording_opt_in and (on_permitted_domain( team, request) or len(team.app_urls) == 0): response["sessionRecording"] = {"endpoint": "/s/"} return cors_response(request, JsonResponse(response))
def get_event_ingestion_context( request, data, token ) -> Tuple[Optional[EventIngestionContext], Optional[str], Optional[Any]]: db_error = None ingestion_context = None error_response = None try: ingestion_context = get_event_ingestion_context_for_token(token) except Exception as e: capture_exception(e) statsd.incr("capture_endpoint_fetch_team_fail") db_error = getattr(e, "message", repr(e)) return None, db_error, error_response if ingestion_context is None: try: project_id = get_project_id(data, request) except ValueError: error_response = cors_response( request, generate_exception_response("capture", "Invalid Project ID.", code="invalid_project", attr="project_id"), ) return None, db_error, error_response if not project_id: error_response = cors_response( request, generate_exception_response( "capture", "Project API key invalid. You can find your project API key in PostHog project settings.", type="authentication_error", code="invalid_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) return None, db_error, error_response ingestion_context = get_event_ingestion_context_for_personal_api_key( personal_api_key=token, project_id=project_id) if ingestion_context is None: error_response = cors_response( request, generate_exception_response( "capture", "Invalid Personal API key.", type="authentication_error", code="invalid_personal_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) return None, db_error, error_response # if we still haven't found a ingestion_context, return an error to the client if not ingestion_context: error_response = cors_response( request, generate_exception_response( "capture", "No team found for API Key", type="authentication_error", code="invalid_personal_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) return ingestion_context, db_error, error_response
def get_event(request): timer = statsd.timer("posthog_cloud_event_endpoint").start() now = timezone.now() try: data = load_data_from_request(request) except RequestParsingError as error: capture_exception( error ) # We still capture this on Sentry to identify actual potential bugs return cors_response( request, generate_exception_response("capture", f"Malformed request data: {error}", code="invalid_payload"), ) if not data: return cors_response( request, generate_exception_response( "capture", "No data found. Make sure to use a POST request when sending the payload in the body of the request.", code="no_data", ), ) sent_at = _get_sent_at(data, request) token = _get_token(data, request) if not token: return cors_response( request, generate_exception_response( "capture", "API key not provided. You can find your project API key in PostHog project settings.", type="authentication_error", code="missing_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) token, is_test_environment = _clean_token(token) assert token is not None team = Team.objects.get_team_from_token(token) if team is None: try: project_id = _get_project_id(data, request) except ValueError: return cors_response( request, generate_exception_response("capture", "Invalid Project ID.", code="invalid_project", attr="project_id"), ) if not project_id: return cors_response( request, generate_exception_response( "capture", "Project API key invalid. You can find your project API key in PostHog project settings.", type="authentication_error", code="invalid_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) user = User.objects.get_from_personal_api_key(token) if user is None: return cors_response( request, generate_exception_response( "capture", "Invalid Personal API key.", type="authentication_error", code="invalid_personal_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) team = user.teams.get(id=project_id) if isinstance(data, dict): if data.get("batch"): # posthog-python and posthog-ruby data = data["batch"] assert data is not None elif "engage" in request.path_info: # JS identify call data["event"] = "$identify" # make sure it has an event name if isinstance(data, list): events = data else: events = [data] try: events = preprocess_session_recording_events(events) except ValueError as e: return cors_response( request, generate_exception_response("capture", f"Invalid payload: {e}", code="invalid_payload")) for event in events: try: distinct_id = _get_distinct_id(event) except KeyError: return cors_response( request, generate_exception_response( "capture", "You need to set user distinct ID field `distinct_id`.", code="required", attr="distinct_id", ), ) except ValueError: return cors_response( request, generate_exception_response( "capture", "Distinct ID field `distinct_id` must have a non-empty value.", code="required", attr="distinct_id", ), ) if not event.get("event"): return cors_response( request, generate_exception_response( "capture", "You need to set user event name, field `event`.", code="required", attr="event"), ) site_url = request.build_absolute_uri("/")[:-1] ip = None if team.anonymize_ips else get_ip_address(request) if not event.get("properties"): event["properties"] = {} # Support test_[apiKey] for users with multiple environments if event["properties"].get( "$environment") is None and is_test_environment: event["properties"]["$environment"] = ENVIRONMENT_TEST _ensure_web_feature_flags_in_properties(event, team, distinct_id) statsd.incr("posthog_cloud_plugin_server_ingestion") capture_internal(event, distinct_id, ip, site_url, now, sent_at, team.pk) timer.stop() statsd.incr(f"posthog_cloud_raw_endpoint_success", tags={ "endpoint": "capture", }) return cors_response(request, JsonResponse({"status": 1}))
def get_decide(request: HttpRequest): response = { "config": { "enable_collect_everything": True }, "editorParams": {}, "isAuthenticated": False, "supportedCompression": ["gzip", "gzip-js", "lz64"], } if request.COOKIES.get(settings.TOOLBAR_COOKIE_NAME): response["isAuthenticated"] = True if settings.JS_URL: response["editorParams"] = { "jsURL": settings.JS_URL, "toolbarVersion": "toolbar" } if request.user.is_authenticated: r, update_user_token = decide_editor_params(request) response.update(r) if update_user_token: request.user.temporary_token = secrets.token_urlsafe(32) request.user.save() response["featureFlags"] = [] response["sessionRecording"] = False if request.method == "POST": try: data_from_request = load_data_from_request(request) data = data_from_request["data"] except (json.decoder.JSONDecodeError, TypeError): return cors_response( request, JsonResponse( { "code": "validation", "message": "Malformed request data. Make sure you're sending valid JSON.", }, status=400, ), ) token = _get_token(data, request) team = Team.objects.get_team_from_token(token) if team is None and token: project_id = _get_project_id(data, request) if not project_id: return cors_response( request, JsonResponse( { "code": "validation", "message": "Project API key invalid. You can find your project API key in PostHog project settings.", }, status=401, ), ) user = User.objects.get_from_personal_api_key(token) if user is None: return cors_response( request, JsonResponse( { "code": "validation", "message": "Personal API key invalid.", }, status=401, ), ) team = user.teams.get(id=project_id) if team: response["featureFlags"] = get_active_feature_flags( team, data_from_request["data"]["distinct_id"]) if team.session_recording_opt_in and (on_permitted_domain( team, request) or len(team.app_urls) == 0): response["sessionRecording"] = {"endpoint": "/s/"} return cors_response(request, JsonResponse(response))
def get_event(request): now = timezone.now() try: data_from_request = load_data_from_request(request) data = data_from_request["data"] except TypeError: return cors_response( request, JsonResponse( { "code": "validation", "message": "Malformed request data. Make sure you're sending valid JSON.", }, status=400, ), ) if not data: return cors_response( request, JsonResponse( { "code": "validation", "message": "No data found. Make sure to use a POST request when sending the payload in the body of the request.", }, status=400, ), ) sent_at = _get_sent_at(data, request) token = _get_token(data, request) is_personal_api_key = False if not token: token = PersonalAPIKeyAuthentication.find_key( request, data_from_request["body"], data if isinstance(data, dict) else None) is_personal_api_key = True if not token: return cors_response( request, JsonResponse( { "code": "validation", "message": "Neither api_key nor personal_api_key set. You can find your project API key in PostHog project settings.", }, status=400, ), ) team = Team.objects.get_team_from_token(token, is_personal_api_key) if team is None: return cors_response( request, JsonResponse( { "code": "validation", "message": "Project or personal API key invalid. You can find your project API key in PostHog project settings.", }, status=400, ), ) if isinstance(data, dict): if data.get("batch"): # posthog-python and posthog-ruby data = data["batch"] assert data is not None elif "engage" in request.path_info: # JS identify call data["event"] = "$identify" # make sure it has an event name if isinstance(data, list): events = data else: events = [data] for event in events: try: distinct_id = _get_distinct_id(event) except KeyError: return cors_response( request, JsonResponse( { "code": "validation", "message": "You need to set user distinct ID field `distinct_id`.", "item": event, }, status=400, ), ) if "event" not in event: return cors_response( request, JsonResponse( { "code": "validation", "message": "You need to set event name field `event`.", "item": event, }, status=400, ), ) if check_ee_enabled(): process_event_ee.delay( distinct_id=distinct_id, ip=get_ip_address(request), site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, ) # log the event to kafka write ahead log for processing log_event( distinct_id=distinct_id, ip=get_ip_address(request), site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, ) else: process_event.delay( distinct_id=distinct_id, ip=get_ip_address(request), site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, ) return cors_response(request, JsonResponse({"status": 1}))
def get_event(request): timer = statsd.timer("posthog_cloud_event_endpoint").start() now = timezone.now() data, error_response = get_data(request) if error_response: return error_response sent_at = _get_sent_at(data, request) token = get_token(data, request) if not token: return cors_response( request, generate_exception_response( "capture", "API key not provided. You can find your project API key in PostHog project settings.", type="authentication_error", code="missing_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) ingestion_context, db_error, error_response = get_event_ingestion_context(request, data, token) if error_response: return error_response send_events_to_dead_letter_queue = False if db_error: send_events_to_dead_letter_queue = True if isinstance(data, dict): if data.get("batch"): # posthog-python and posthog-ruby data = data["batch"] assert data is not None elif "engage" in request.path_info: # JS identify call data["event"] = "$identify" # make sure it has an event name if isinstance(data, list): events = data else: events = [data] try: events = preprocess_session_recording_events(events) except ValueError as e: return cors_response( request, generate_exception_response("capture", f"Invalid payload: {e}", code="invalid_payload") ) site_url = request.build_absolute_uri("/")[:-1] ip = None if not ingestion_context or ingestion_context.anonymize_ips else get_ip_address(request) for event in events: event_uuid = UUIDT() distinct_id = get_distinct_id(event) if not distinct_id: continue payload_uuid = event.get("uuid", None) if payload_uuid: if UUIDT.is_valid_uuid(payload_uuid): event_uuid = UUIDT(uuid_str=payload_uuid) else: statsd.incr("invalid_event_uuid") event = parse_event(event, distinct_id, ingestion_context) if not event: continue if send_events_to_dead_letter_queue: kafka_event = parse_kafka_event_data( distinct_id=distinct_id, ip=None, site_url=site_url, team_id=None, now=now, event_uuid=event_uuid, data=event, sent_at=sent_at, ) log_event_to_dead_letter_queue( data, event["event"], kafka_event, f"Unable to fetch team from Postgres. Error: {db_error}", "django_server_capture_endpoint", ) continue try: capture_internal(event, distinct_id, ip, site_url, now, sent_at, ingestion_context.team_id, event_uuid) # type: ignore except Exception as e: timer.stop() capture_exception(e, {"data": data}) statsd.incr( "posthog_cloud_raw_endpoint_failure", tags={"endpoint": "capture",}, ) return cors_response( request, generate_exception_response( "capture", "Unable to store event. Please try again. If you are the owner of this app you can check the logs for further details.", code="server_error", type="server_error", status_code=status.HTTP_503_SERVICE_UNAVAILABLE, ), ) timer.stop() statsd.incr( "posthog_cloud_raw_endpoint_success", tags={"endpoint": "capture",}, ) return cors_response(request, JsonResponse({"status": 1}))
def get_decide(request: HttpRequest): response = { "config": { "enable_collect_everything": True }, "editorParams": {}, "isAuthenticated": False, "supportedCompression": ["gzip", "gzip-js", "lz64"], } if request.COOKIES.get( settings.TOOLBAR_COOKIE_NAME) and request.user.is_authenticated: response["isAuthenticated"] = True if settings.JS_URL and request.user.toolbar_mode == User.TOOLBAR: response["editorParams"] = { "jsURL": settings.JS_URL, "toolbarVersion": "toolbar" } if request.user.is_authenticated: r, update_user_token = decide_editor_params(request) response.update(r) if update_user_token: request.user.temporary_token = secrets.token_urlsafe(32) request.user.save() response["featureFlags"] = [] response["sessionRecording"] = False if request.method == "POST": try: data = load_data_from_request(request) api_version_string = request.GET.get("v") # NOTE: This does not support semantic versioning e.g. 2.1.0 api_version = int(api_version_string) if api_version_string else 1 except ValueError: # default value added because of bug in posthog-js 1.19.0 # see https://sentry.io/organizations/posthog2/issues/2738865125/?project=1899813 # as a tombstone if the below statsd counter hasn't seen errors for N days # then it is likely that no clients are running posthog-js 1.19.0 # and this defaulting could be removed statsd.incr( f"posthog_cloud_decide_defaulted_api_version_on_value_error", tags={ "endpoint": "decide", "api_version_string": api_version_string }, ) api_version = 2 except RequestParsingError as error: capture_exception( error ) # We still capture this on Sentry to identify actual potential bugs return cors_response( request, generate_exception_response("decide", f"Malformed request data: {error}", code="malformed_data"), ) token = get_token(data, request) team = Team.objects.get_team_from_token(token) if team is None and token: project_id = get_project_id(data, request) if not project_id: return cors_response( request, generate_exception_response( "decide", "Project API key invalid. You can find your project API key in PostHog project settings.", code="invalid_api_key", type="authentication_error", status_code=status.HTTP_401_UNAUTHORIZED, ), ) user = User.objects.get_from_personal_api_key(token) if user is None: return cors_response( request, generate_exception_response( "decide", "Invalid Personal API key.", code="invalid_personal_key", type="authentication_error", status_code=status.HTTP_401_UNAUTHORIZED, ), ) team = user.teams.get(id=project_id) if team: feature_flags = get_overridden_feature_flags( team.pk, data["distinct_id"], data.get("groups", {})) response[ "featureFlags"] = feature_flags if api_version >= 2 else list( feature_flags.keys()) if team.session_recording_opt_in and (on_permitted_domain( team, request) or len(team.app_urls) == 0): response["sessionRecording"] = {"endpoint": "/s/"} statsd.incr( f"posthog_cloud_raw_endpoint_success", tags={ "endpoint": "decide", }, ) return cors_response(request, JsonResponse(response))
def get_team(request, data, token) -> Tuple[Optional[Team], Optional[str], Optional[Any]]: db_error = None team = None error_response = None try: team = Team.objects.get_team_from_token(token) except Exception as e: capture_exception(e) statsd.incr("capture_endpoint_fetch_team_fail") db_error = getattr(e, "message", repr(e)) if not is_clickhouse_enabled(): error_response = cors_response( request, generate_exception_response( "capture", "Unable to fetch team from database.", type="server_error", code="fetch_team_fail", status_code=status.HTTP_503_SERVICE_UNAVAILABLE, ), ) return None, db_error, error_response if team is None: try: project_id = get_project_id(data, request) except ValueError: error_response = cors_response( request, generate_exception_response("capture", "Invalid Project ID.", code="invalid_project", attr="project_id"), ) return None, db_error, error_response if not project_id: error_response = cors_response( request, generate_exception_response( "capture", "Project API key invalid. You can find your project API key in PostHog project settings.", type="authentication_error", code="invalid_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) return None, db_error, error_response user = User.objects.get_from_personal_api_key(token) if user is None: error_response = cors_response( request, generate_exception_response( "capture", "Invalid Personal API key.", type="authentication_error", code="invalid_personal_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) return None, db_error, error_response team = user.teams.get(id=project_id) # if we still haven't found a team, return an error to the client if not team: error_response = cors_response( request, generate_exception_response( "capture", "No team found for API Key", type="authentication_error", code="invalid_personal_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) return team, db_error, error_response
def get_event(request): now = timezone.now() data = _load_data(request) if not data: return cors_response(request, HttpResponse("1")) sent_at = _get_sent_at(data, request) token = _get_token(data, request) if not token: return cors_response( request, JsonResponse( { "code": "validation", "message": "No api_key set. You can find your API key in the /setup page in posthog", }, status=400, ), ) try: team_id = Team.objects.get_cached_from_token(token).pk except Team.DoesNotExist: return cors_response( request, JsonResponse( { "code": "validation", "message": "API key is incorrect. You can find your API key in the /setup page in PostHog.", }, status=400, ), ) if isinstance(data, dict): if data.get("batch"): # posthog-python and posthog-ruby data = data["batch"] assert data is not None elif "engage" in request.path_info: # JS identify call data["event"] = "$identify" # make sure it has an event name if isinstance(data, list): events = data else: events = [data] for event in events: try: distinct_id = _get_distinct_id(event) except KeyError: return cors_response( request, JsonResponse( { "code": "validation", "message": "You need to set a distinct_id.", "item": event, }, status=400, ), ) process_event.delay( distinct_id=distinct_id, ip=get_ip_address(request), site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team_id, now=now, sent_at=sent_at, ) return cors_response(request, JsonResponse({"status": 1}))
def get_event(request): timer = statsd.Timer("%s_posthog_cloud" % (settings.STATSD_PREFIX,)) timer.start() now = timezone.now() try: data_from_request = load_data_from_request(request) data = data_from_request["data"] except TypeError: return cors_response( request, JsonResponse( {"code": "validation", "message": "Malformed request data. Make sure you're sending valid JSON.",}, status=400, ), ) if not data: return cors_response( request, JsonResponse( { "code": "validation", "message": "No data found. Make sure to use a POST request when sending the payload in the body of the request.", }, status=400, ), ) sent_at = _get_sent_at(data, request) token = _get_token(data, request) if not token: return cors_response( request, JsonResponse( { "code": "validation", "message": "API key not provided. You can find your project API key in PostHog project settings.", }, status=400, ), ) team = Team.objects.get_team_from_token(token) if team is None: try: project_id = _get_project_id(data, request) except: return cors_response( request, JsonResponse({"code": "validation", "message": "Invalid project ID.",}, status=400,), ) if not project_id: return cors_response( request, JsonResponse( { "code": "validation", "message": "Project API key invalid. You can find your project API key in PostHog project settings.", }, status=400, ), ) user = User.objects.get_from_personal_api_key(token) if user is None: return cors_response( request, JsonResponse({"code": "validation", "message": "Personal API key invalid.",}, status=400,), ) team = user.teams.get(id=project_id) if isinstance(data, dict): if data.get("batch"): # posthog-python and posthog-ruby data = data["batch"] assert data is not None elif "engage" in request.path_info: # JS identify call data["event"] = "$identify" # make sure it has an event name if isinstance(data, list): events = data else: events = [data] for event in events: try: distinct_id = _get_distinct_id(event) except KeyError: return cors_response( request, JsonResponse( { "code": "validation", "message": "You need to set user distinct ID field `distinct_id`.", "item": event, }, status=400, ), ) if "event" not in event: return cors_response( request, JsonResponse( {"code": "validation", "message": "You need to set event name field `event`.", "item": event,}, status=400, ), ) if is_ee_enabled(): process_event_ee( distinct_id=distinct_id, ip=get_ip_address(request), site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, ) else: task_name = "posthog.tasks.process_event.process_event" celery_queue = settings.CELERY_DEFAULT_QUEUE if team.plugins_opt_in: task_name += "_with_plugins" celery_queue = settings.PLUGINS_CELERY_QUEUE celery_app.send_task( name=task_name, queue=celery_queue, args=[ distinct_id, get_ip_address(request), request.build_absolute_uri("/")[:-1], event, team.id, now.isoformat(), sent_at, ], ) if is_ee_enabled() and settings.LOG_TO_WAL: # log the event to kafka write ahead log for processing log_event( distinct_id=distinct_id, ip=get_ip_address(request), site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, ) timer.stop("event_endpoint") return cors_response(request, JsonResponse({"status": 1}))
def get_event(request): timer = statsd.Timer("%s_posthog_cloud" % (settings.STATSD_PREFIX,)) timer.start() now = timezone.now() try: data = load_data_from_request(request) except RequestParsingError as error: capture_exception(error) # We still capture this on Sentry to identify actual potential bugs return cors_response( request, generate_exception_response(f"Malformed request data: {error}", code="invalid_payload"), ) if not data: return cors_response( request, generate_exception_response( "No data found. Make sure to use a POST request when sending the payload in the body of the request.", code="no_data", ), ) sent_at = _get_sent_at(data, request) token = _get_token(data, request) if not token: return cors_response( request, generate_exception_response( "API key not provided. You can find your project API key in PostHog project settings.", type="authentication_error", code="missing_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) team = Team.objects.get_team_from_token(token) if team is None: try: project_id = _get_project_id(data, request) except ValueError: return cors_response( request, generate_exception_response("Invalid Project ID.", code="invalid_project", attr="project_id"), ) if not project_id: return cors_response( request, generate_exception_response( "Project API key invalid. You can find your project API key in PostHog project settings.", type="authentication_error", code="invalid_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) user = User.objects.get_from_personal_api_key(token) if user is None: return cors_response( request, generate_exception_response( "Invalid Personal API key.", type="authentication_error", code="invalid_personal_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) team = user.teams.get(id=project_id) if isinstance(data, dict): if data.get("batch"): # posthog-python and posthog-ruby data = data["batch"] assert data is not None elif "engage" in request.path_info: # JS identify call data["event"] = "$identify" # make sure it has an event name if isinstance(data, list): events = data else: events = [data] try: events = preprocess_session_recording_events(events) except ValueError as e: return cors_response(request, generate_exception_response(f"Invalid payload: {e}", code="invalid_payload")) for event in events: try: distinct_id = _get_distinct_id(event) except KeyError: return cors_response( request, generate_exception_response( "You need to set user distinct ID field `distinct_id`.", code="required", attr="distinct_id" ), ) if not event.get("event"): return cors_response( request, generate_exception_response( "You need to set user event name, field `event`.", code="required", attr="event" ), ) if not event.get("properties"): event["properties"] = {} _ensure_web_feature_flags_in_properties(event, team, distinct_id) event_uuid = UUIDT() ip = None if team.anonymize_ips else get_ip_address(request) if is_ee_enabled(): log_topics = [KAFKA_EVENTS_WAL] if settings.PLUGIN_SERVER_INGESTION: log_topics.append(KAFKA_EVENTS_PLUGIN_INGESTION) statsd.Counter("%s_posthog_cloud_plugin_server_ingestion" % (settings.STATSD_PREFIX,)).increment() log_event( distinct_id=distinct_id, ip=ip, site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, event_uuid=event_uuid, topics=log_topics, ) # must done after logging because process_event_ee modifies the event, e.g. by removing $elements if not settings.PLUGIN_SERVER_INGESTION: process_event_ee( distinct_id=distinct_id, ip=ip, site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, event_uuid=event_uuid, ) else: task_name = "posthog.tasks.process_event.process_event_with_plugins" celery_queue = settings.PLUGINS_CELERY_QUEUE celery_app.send_task( name=task_name, queue=celery_queue, args=[distinct_id, ip, request.build_absolute_uri("/")[:-1], event, team.id, now.isoformat(), sent_at,], ) timer.stop("event_endpoint") return cors_response(request, JsonResponse({"status": 1}))
def get_event(request): timer = statsd.Timer("%s_posthog_cloud" % (settings.STATSD_PREFIX, )) timer.start() now = timezone.now() try: data_from_request = load_data_from_request(request) data = data_from_request["data"] except TypeError: return cors_response( request, JsonResponse( { "code": "validation", "message": "Malformed request data. Make sure you're sending valid JSON.", }, status=400, ), ) if not data: return cors_response( request, JsonResponse( { "code": "validation", "message": "No data found. Make sure to use a POST request when sending the payload in the body of the request.", }, status=400, ), ) sent_at = _get_sent_at(data, request) token = _get_token(data, request) if not token: return cors_response( request, JsonResponse( { "code": "validation", "message": "API key not provided. You can find your project API key in PostHog project settings.", }, status=401, ), ) team = Team.objects.get_team_from_token(token) if team is None: try: project_id = _get_project_id(data, request) except: return cors_response( request, JsonResponse( { "code": "validation", "message": "Invalid project ID.", }, status=400, ), ) if not project_id: return cors_response( request, JsonResponse( { "code": "validation", "message": "Project API key invalid. You can find your project API key in PostHog project settings.", }, status=401, ), ) user = User.objects.get_from_personal_api_key(token) if user is None: return cors_response( request, JsonResponse( { "code": "validation", "message": "Personal API key invalid.", }, status=401, ), ) team = user.teams.get(id=project_id) if isinstance(data, dict): if data.get("batch"): # posthog-python and posthog-ruby data = data["batch"] assert data is not None elif "engage" in request.path_info: # JS identify call data["event"] = "$identify" # make sure it has an event name if isinstance(data, list): events = data else: events = [data] for event in events: try: distinct_id = _get_distinct_id(event) except KeyError: return cors_response( request, JsonResponse( { "code": "validation", "message": "You need to set user distinct ID field `distinct_id`.", "item": event, }, status=400, ), ) if not event.get("event"): return cors_response( request, JsonResponse( { "code": "validation", "message": "You need to set event name field `event`.", "item": event, }, status=400, ), ) if not event.get("properties"): event["properties"] = {} _ensure_web_feature_flags_in_properties(event, team, distinct_id) event_uuid = UUIDT() if is_ee_enabled(): log_topics = [KAFKA_EVENTS_WAL] if settings.PLUGIN_SERVER_INGESTION: log_topics.append(KAFKA_EVENTS_PLUGIN_INGESTION) statsd.Counter("%s_posthog_cloud_plugin_server_ingestion" % (settings.STATSD_PREFIX, )).increment() log_event( distinct_id=distinct_id, ip=get_ip_address(request), site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, event_uuid=event_uuid, topics=log_topics, ) # must done after logging because process_event_ee modifies the event, e.g. by removing $elements if not settings.PLUGIN_SERVER_INGESTION: process_event_ee( distinct_id=distinct_id, ip=get_ip_address(request), site_url=request.build_absolute_uri("/")[:-1], data=event, team_id=team.id, now=now, sent_at=sent_at, event_uuid=event_uuid, ) else: task_name = "posthog.tasks.process_event.process_event" if settings.PLUGIN_SERVER_INGESTION or team.plugins_opt_in: task_name += "_with_plugins" celery_queue = settings.PLUGINS_CELERY_QUEUE else: celery_queue = settings.CELERY_DEFAULT_QUEUE celery_app.send_task( name=task_name, queue=celery_queue, args=[ distinct_id, get_ip_address(request), request.build_absolute_uri("/")[:-1], event, team.id, now.isoformat(), sent_at, ], ) timer.stop("event_endpoint") return cors_response(request, JsonResponse({"status": 1}))
def get_event(request): timer = statsd.timer("posthog_cloud_event_endpoint").start() now = timezone.now() data, error_response = get_data(request) if error_response: return error_response sent_at = _get_sent_at(data, request) token = get_token(data, request) if not token: return cors_response( request, generate_exception_response( "capture", "API key not provided. You can find your project API key in PostHog project settings.", type="authentication_error", code="missing_api_key", status_code=status.HTTP_401_UNAUTHORIZED, ), ) team, db_error, error_response = get_team(request, data, token) if error_response: return error_response send_events_to_dead_letter_queue = False if db_error and is_clickhouse_enabled(): send_events_to_dead_letter_queue = True if isinstance(data, dict): if data.get("batch"): # posthog-python and posthog-ruby data = data["batch"] assert data is not None elif "engage" in request.path_info: # JS identify call data["event"] = "$identify" # make sure it has an event name if isinstance(data, list): events = data else: events = [data] try: events = preprocess_session_recording_events(events) except ValueError as e: return cors_response( request, generate_exception_response("capture", f"Invalid payload: {e}", code="invalid_payload")) site_url = request.build_absolute_uri("/")[:-1] ip = None if not team or team.anonymize_ips else get_ip_address(request) for event in events: event_uuid = UUIDT() distinct_id = get_distinct_id(event) if not distinct_id: continue event = parse_event(event, distinct_id, team) if not event: continue if send_events_to_dead_letter_queue: kafka_event = parse_kafka_event_data( distinct_id=distinct_id, ip=None, site_url=site_url, team_id=None, now=now, event_uuid=event_uuid, data=event, sent_at=sent_at, ) log_event_to_dead_letter_queue( data, event["event"], kafka_event, f"Unable to fetch team from Postgres. Error: {db_error}", "django_server_capture_endpoint", ) continue statsd.incr("posthog_cloud_plugin_server_ingestion") capture_internal(event, distinct_id, ip, site_url, now, sent_at, team.pk, event_uuid) # type: ignore timer.stop() statsd.incr( f"posthog_cloud_raw_endpoint_success", tags={ "endpoint": "capture", }, ) return cors_response(request, JsonResponse({"status": 1}))