public function track($event, $properties, $opts = array()) {
        $this->mergeArguments($opts, array(
            'return_score' => false,
            'return_action' => false,
            'return_workflow_status' => false,
            'force_workflow_run' => false,
            'abuse_types' => array(),
            'path' => null,
            'timeout' => $this->timeout,
            'version' => $this->version
        ));
        $this->validateArgument($event, 'event', 'string');
        $this->validateArgument($properties, 'properties', 'array');

        $path = $opts['path'];
        if (!$path) {
            $path = self::restApiUrl($opts['version']);
        }

        $properties['$api_key'] = $this->api_key;
        $properties['$type'] = $event;

        $params = array();
        if ($opts['return_score']) $params['return_score'] = 'true';
        if ($opts['return_action']) $params['return_action'] = 'true';
        if ($opts['return_workflow_status']) $params['return_workflow_status'] = 'true';
        if ($opts['force_workflow_run']) $params['force_workflow_run'] = 'true';
        if ($opts['abuse_types']) $params['abuse_types'] = implode(',', $opts['abuse_types']);

        try {
            $request = new SiftRequest(
                $path, SiftRequest::POST, $opts['timeout'], $opts['version'], array(
                    'body' => $properties,
                    'params' => $params
                ),
                $this->curl_opts
            );
            return $request->send();
        } catch (Exception $e) {
            $this->logError($e->getMessage());
            return null;
        }
    }