diff --git a/config/conf.yml b/config/conf.yml
index fc22322..4168638 100644
--- a/config/conf.yml
+++ b/config/conf.yml
@@ -28,4 +28,7 @@ logging:
   datefmt: "%Y-%m-%d %H:%M:%S"
 host: localhost
 port: 5921
-lbrynet: http://localhost:5279
\ No newline at end of file
+lbrynet: http://localhost:5279
+notifications:
+  url: https://api.lbry.com/event/comment
+  auth_token: mytoken
\ No newline at end of file
diff --git a/src/database/comments_ddl.sql b/src/database/comments_ddl.sql
index f488406..cebe0c2 100644
--- a/src/database/comments_ddl.sql
+++ b/src/database/comments_ddl.sql
@@ -48,3 +48,33 @@ ALTER TABLE COMMENT
 
 CREATE INDEX `claim_comment_index` ON `COMMENT` (`lbryclaimid`, `commentid`);
 CREATE INDEX `channel_comment_index` ON `COMMENT` (`channelid`, `commentid`);
+
+CREATE TABLE `COMMENTOPINION` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
+  `commentid` char(64) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `channelid` char(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `signature` char(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `signingts` varchar(22) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `timestamp` int NOT NULL,
+  `rating` tinyint DEFAULT '1',
+
+  PRIMARY KEY (`id`),
+  KEY `comment_channel_fk` (`channelid`),
+  CONSTRAINT `comment_fk` FOREIGN KEY (`commentid`) REFERENCES `COMMENT` (`commentid`) ON DELETE CASCADE ON UPDATE CASCADE,
+  CONSTRAINT `comment_channel_fk` FOREIGN KEY (`channelid`) REFERENCES `CHANNEL` (`claimid`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
+
+CREATE TABLE `CONTENTOPINION` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
+  `claimid` char(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `channelid` char(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `signature` char(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `signingts` varchar(22) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `timestamp` int NOT NULL,
+  `rating` tinyint DEFAULT '1',
+
+  PRIMARY KEY (`id`),
+  KEY `comment_channel_fk` (`channelid`),
+  KEY `lbryclaimid` (`lbryclaimid`),
+  CONSTRAINT `comment_channel_fk` FOREIGN KEY (`channelid`) REFERENCES `CHANNEL` (`claimid`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
\ No newline at end of file
diff --git a/src/database/models.py b/src/database/models.py
index b35f122..f0ddfc2 100644
--- a/src/database/models.py
+++ b/src/database/models.py
@@ -51,6 +51,50 @@ class Comment(Model):
         )
 
 
+class CommentOpinion(Model):
+    id = BigAutoField(column_name='id', primary_key=True)
+    comment = ForeignKeyField(
+        backref='opinions',
+        column_name='commentid',
+        field='comment_id',
+        model=Comment,
+        null=True
+    )
+    channel = ForeignKeyField(
+        backref='opinions',
+        column_name='channelid',
+        field='claim_id',
+        model=Channel,
+        null=True
+    )
+    signature = FixedCharField(max_length=128, column_name='signature', null=True, unique=True)
+    signing_ts = TextField(column_name='signingts', null=True)
+    timestamp = IntegerField(column_name='timestamp')
+    rating = SmallIntegerField(column_name='rating', null=False)
+
+    class Meta:
+        table_name = 'COMMENTOPINION'
+
+
+class ContentOpinion(Model):
+    id = BigAutoField(column_name='id', primary_key=True)
+    channel = ForeignKeyField(
+        backref='opinions',
+        column_name='channelid',
+        field='claim_id',
+        model=Channel,
+        null=True
+    )
+    claim_id = FixedCharField(max_length=40, column_name='lbryclaimid')
+    signature = FixedCharField(max_length=128, column_name='signature', null=True, unique=True)
+    signing_ts = TextField(column_name='signingts', null=True)
+    timestamp = IntegerField(column_name='timestamp')
+    rating = SmallIntegerField(column_name='rating', null=False, default=0)
+
+    class Meta:
+        table_name = 'CONTENTOPINION'
+
+
 FIELDS = {
     'comment': Comment.comment,
     'comment_id': Comment.comment_id,
@@ -66,17 +110,21 @@ FIELDS = {
 }
 
 
-def comment_list(claim_id: str = None, parent_id: str = None,
-                 top_level: bool = False, exclude_mode: str = None,
-                 page: int = 1, page_size: int = 50, expressions=None,
-                 select_fields: list = None, exclude_fields: list = None) -> dict:
+def comment_list(claim_id: str = None,
+                 parent_id: str = None,
+                 top_level: bool = False,
+                 exclude_mode: str = None,
+                 page: int = 1,
+                 page_size: int = 50, expressions=None,
+                 select_fields: list = None,
+                 exclude_fields: list = None) -> dict:
     fields = FIELDS.keys()
     if exclude_fields:
         fields -= set(exclude_fields)
     if select_fields:
         fields &= set(select_fields)
     attributes = [FIELDS[field] for field in fields]
-    query = Comment.select(*attributes)
+    query = Comment.select(fn.SUM(CommentOpinion.rating).alias('rating'), *attributes)
 
     # todo: allow this process to be more automated, so it can just be an expression
     if claim_id:
@@ -97,6 +145,8 @@ def comment_list(claim_id: str = None, parent_id: str = None,
     total = query.count()
     query = (query
              .join(Channel, JOIN.LEFT_OUTER)
+             .join(CommentOpinion, JOIN.LEFT_OUTER)
+             .group_by(Comment.comment_id)
              .order_by(Comment.timestamp.desc())
              .paginate(page, page_size))
     items = [clean(item) for item in query.dicts()]
@@ -203,6 +253,26 @@ def set_hidden_flag(comment_ids: typing.List[str], hidden=True) -> bool:
     return update.execute() > 0
 
 
+def create_comment_opinion(comment_id: str = None,
+                   channel_id: str = None,
+                   channel_name: str = None,
+                   signature: str = None,
+                   signing_ts: str = None,
+                   rating: int = None) -> dict:
+
+    channel, _ = Channel.get_or_create(name=channel_name, claim_id=channel_id)
+
+    timestamp = int(time.time())
+    new_comment_opinion = CommentOpinion.create(
+            commentid=comment_id,
+            channel=channel,
+            signature=signature,
+            signing_ts=signing_ts,
+            timestamp=timestamp,
+            rating=rating,
+        )
+    return new_comment_opinion
+
 if __name__ == '__main__':
     logger = logging.getLogger('peewee')
     logger.addHandler(logging.StreamHandler())
diff --git a/src/server/app.py b/src/server/app.py
index 37e89da..6aecf9a 100644
--- a/src/server/app.py
+++ b/src/server/app.py
@@ -10,9 +10,9 @@ from aiohttp import web
 
 from peewee import *
 from src.server.handles import api_endpoint, get_api_endpoint
-from src.database.models import Comment, Channel
+from src.database.models import Comment, Channel, CommentOpinion, ContentOpinion
 
-MODELS = [Comment, Channel]
+MODELS = [Comment, Channel, CommentOpinion, ContentOpinion]
 logger = logging.getLogger(__name__)
 
 
diff --git a/src/server/handles.py b/src/server/handles.py
index a7fe43b..b8c8952 100644
--- a/src/server/handles.py
+++ b/src/server/handles.py
@@ -11,7 +11,7 @@ from src.server.external import send_notification
 from src.server.validation import validate_signature_from_claim
 from src.misc import clean_input_params, get_claim_from_id
 from src.server.errors import make_error, report_error
-from src.database.models import Comment, Channel
+from src.database.models import Comment, Channel, create_comment_opinion
 from src.database.models import get_comment
 from src.database.models import comment_list
 from src.database.models import create_comment
@@ -19,7 +19,6 @@ from src.database.models import edit_comment
 from src.database.models import delete_comment
 from src.database.models import set_hidden_flag
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -151,10 +150,10 @@ async def handle_hide_comments(app: web.Application, pieces: list, hide: bool =
                 channel = claims[claim_id]['signing_channel']
                 piece = pieces_by_id[comment_id]
                 is_valid_signature = validate_signature_from_claim(
-                        claim=channel,
-                        signature=piece['signature'],
-                        signing_ts=piece['signing_ts'],
-                        data=piece['comment_id']
+                    claim=channel,
+                    signature=piece['signature'],
+                    signing_ts=piece['signing_ts'],
+                    data=piece['comment_id']
                 )
                 if not is_valid_signature:
                     raise ValueError(f'could not validate signature on comment_id: {comment_id}')
@@ -192,8 +191,8 @@ async def handle_edit_comment(app, comment: str = None, comment_id: str = None,
 
 # TODO: retrieve stake amounts for each channel & store in db
 async def handle_create_comment(app, comment: str = None, claim_id: str = None,
-                          parent_id: str = None, channel_id: str = None, channel_name: str = None,
-                          signature: str = None, signing_ts: str = None) -> dict:
+                                parent_id: str = None, channel_id: str = None, channel_name: str = None,
+                                signature: str = None, signing_ts: str = None) -> dict:
     with app['db'].atomic():
         comment = create_comment(
             comment=comment,
@@ -208,18 +207,37 @@ async def handle_create_comment(app, comment: str = None, claim_id: str = None,
         return comment
 
 
+async def handle_create_comment_opinion(app,
+                                        comment_id: str = None,
+                                        channel_id: str = None,
+                                        channel_name: str = None,
+                                        signature: str = None,
+                                        signing_ts: str = None,
+                                        rating: int = None) -> dict:
+    with app['db'].atomic():
+        return create_comment_opinion(
+            comment_id=comment_id,
+            channel_id=channel_id,
+            channel_name=channel_name,
+            signature=signature,
+            signing_ts=signing_ts,
+            rating=rating,
+        )
+
+
 METHODS = {
     'ping': ping,
-    'get_claim_comments': handle_get_claim_comments,    # this gets used
+    'get_claim_comments': handle_get_claim_comments,  # this gets used
     'get_claim_hidden_comments': handle_get_claim_hidden_comments,  # this gets used
     'get_comment_ids': handle_get_comment_ids,
-    'get_comments_by_id': handle_get_comments_by_id,    # this gets used
+    'get_comments_by_id': handle_get_comments_by_id,  # this gets used
     'get_channel_from_comment_id': handle_get_channel_from_comment_id,  # this gets used
-    'create_comment': handle_create_comment,   # this gets used
+    'create_comment': handle_create_comment,  # this gets used
+    'create_comment_opinion': handle_create_comment_opinion,  # this gets used
     'delete_comment': handle_abandon_comment,
     'abandon_comment': handle_abandon_comment,  # this gets used
     'hide_comments': handle_hide_comments,  # this gets used
-    'edit_comment': handle_edit_comment     # this gets used
+    'edit_comment': handle_edit_comment  # this gets used
 }
 
 
diff --git a/test/http_requests/comment-request_local.http b/test/http_requests/comment-request_local.http
new file mode 100644
index 0000000..98018c7
--- /dev/null
+++ b/test/http_requests/comment-request_local.http
@@ -0,0 +1,21 @@
+# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
+#
+# Following HTTP Request Live Templates are available:
+# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
+# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
+# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
+POST http://localhost:5921/api
+Content-Type: application/json
+
+{
+  "jsonrpc": "2.0",
+  "id": null,
+  "method": "get_claim_comments",
+  "params": {
+    "claim_id": "6d266af6c25c80fa2ac6cc7662921ad2e90a07e7"
+  }
+}
+
+
+
+###
diff --git a/test/http_requests/create-comment-opinion-local.http b/test/http_requests/create-comment-opinion-local.http
new file mode 100644
index 0000000..50f743f
--- /dev/null
+++ b/test/http_requests/create-comment-opinion-local.http
@@ -0,0 +1,24 @@
+# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
+#
+# Following HTTP Request Live Templates are available:
+# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
+# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
+# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
+POST http://localhost:5921/api
+Content-Type: application/json
+
+{
+  "jsonrpc": "2.0",
+  "id": null,
+  "method": "create_comment_opinion",
+  "params": {
+    "comment_id": "34c7b8266e12eb4887da31ef5401c36484e5c450e979736c1caa19a0d2fd1470",
+    "channel_name": "@bbbbb",
+    "channel_id": "9cb713f01bf247a0e03170b5ed00d5161340c486",
+    "signing_ts": "1234567",
+    "rating": 1
+  }
+}
+
+###
+
diff --git a/test/http_requests/create_comment_local.http b/test/http_requests/create_comment_local.http
index f73c488..0b0b4f8 100644
--- a/test/http_requests/create_comment_local.http
+++ b/test/http_requests/create_comment_local.http
@@ -4,7 +4,7 @@
 # * 'gtrp' and 'gtr' create a GET request with or without query parameters;
 # * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
 # * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
-POST http://localhost:5922/api
+POST http://localhost:5921/api
 Content-Type: application/json
 
 {