diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py
index edc0449af6cca4fbc44a1d5b382a1a2104b3cca9..b88f83f5523074e8496144e174f2b2e48f6c2f4b 100644
--- a/common/lib/xmodule/xmodule/capa_base.py
+++ b/common/lib/xmodule/xmodule/capa_base.py
@@ -844,8 +844,8 @@ class CapaMixin(CapaFields):
         score = self.lcp.get_score()
         self.runtime.publish(
             self,
+            'grade',
             {
-                'event_name': 'grade',
                 'value': score['score'],
                 'max_value': score['total'],
             }
diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py
index dc2fcf06b7b88cadd7f313ca13d3759f90b5f7b8..5e2bf900a778edd24ca09147635602a707750407 100644
--- a/common/lib/xmodule/xmodule/lti_module.py
+++ b/common/lib/xmodule/xmodule/lti_module.py
@@ -533,12 +533,12 @@ oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'}
         if action == 'replaceResultRequest':
             self.system.publish(
                 self,
+                'grade',
                 {
-                    'event_name': 'grade',
                     'value': score * self.max_score(),
                     'max_value': self.max_score(),
-                },
-                custom_user=real_user
+                    'user_id': real_user.id,
+                }
             )
 
             values = {
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index 38bb8541523836f425569ef9ac384cf37498e631..1e3bebcc96e19a5df073f15dbf0f53ba9a8a56d2 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -1059,11 +1059,13 @@ class DescriptorSystem(ConfigurableFragmentWrapper, Runtime):  # pylint: disable
         """
         raise NotImplementedError("edX Platform doesn't currently implement XBlock resource urls")
 
-    def publish(self, block, event):
+    def publish(self, block, event_type, event):
         """
         See :meth:`xblock.runtime.Runtime:publish` for documentation.
         """
-        raise NotImplementedError("edX Platform doesn't currently implement XBlock publish")
+        xmodule_runtime = getattr(block, 'xmodule_runtime', None)
+        if xmodule_runtime is not None:
+            return xmodule_runtime.publish(block, event_type, event)
 
     def add_block_as_child_node(self, block, node):
         child = etree.SubElement(node, "unknown")
@@ -1228,7 +1230,7 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime):  # pylint: disable=abs
     def resource_url(self, resource):
         raise NotImplementedError("edX Platform doesn't currently implement XBlock resource urls")
 
-    def publish(self, block, event):
+    def publish(self, block, event_type, event):
         pass
 
 
diff --git a/docs/en_us/developers/source/xblocks.rst b/docs/en_us/developers/source/xblocks.rst
index 7e7aee22f3b6eeb961bf356795af84e789435a8d..2a15ea53cb0c796e0932b49f9cdf3d46d99dd31e 100644
--- a/docs/en_us/developers/source/xblocks.rst
+++ b/docs/en_us/developers/source/xblocks.rst
@@ -21,8 +21,13 @@ These are properties and methods available on ``self.runtime`` when a view or ha
   that the block is being executed in. The same student in two different courses
   will have two different ids.
 
-* publish(event): Emit events to the surrounding system. Events are dictionaries with
-  at least the key 'event_type', which identifies the other fields.
+* publish(event): Emit events to the surrounding system. Events are dictionaries that can contain arbitrary data.
+  XBlocks can publish events by calling ``self.runtime.publish(self, event_type, event)``. The ``event_type`` parameter
+  enables downstream processing of the event since it uniquely identifies the schema. This call will cause the runtime
+  to save the event data in the application event stream. XBlocks should publish events whenever a significant state
+  change occurs. Post-hoc analysis of the event stream can yield insight about how the XBlock is used in the context of
+  the application. Ideally interesting state of the XBlock could be reconstructed at any point in history through
+  careful analysis of the event stream.
 
   TODO: Link to the authoritive list of event types.
 
@@ -51,12 +56,14 @@ should ``publish`` a ``grade`` event whenever the grade changes. The ``grade`` e
 dictionary of the following form::
 
     {
-        'event_type': 'grade',
         'value': <number>,
         'max_value': <number>,
+        'user_id': <number>,
     }
 
-The grade event represents a grade of ``value/max_value`` for the current user.
+The grade event represents a grade of ``value/max_value`` for the current user. The
+``user_id`` field is optional, the currently logged in user's ID will be used if it is
+omitted.
 
 Restrictions
 ~~~~~~~~~~~~
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 4954a9bf4be2f3ce0b409d83a7a82f5ade0ad70f..9e6f246688d8dcb5573303cbacc22b939b635882 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -292,15 +292,8 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
                                                   position, wrap_xmodule_display, grade_bucket_type,
                                                   static_asset_path)
 
-    def publish(block, event, custom_user=None):
-        """A function that allows XModules to publish events. This only supports grade changes right now."""
-        if event.get('event_name') != 'grade':
-            return
-
-        if custom_user:
-            user_id = custom_user.id
-        else:
-            user_id = user.id
+    def handle_grade_event(block, event_type, event):
+        user_id = event.get('user_id', user.id)
 
         # Construct the key for the module
         key = KeyValueStore.Key(
@@ -333,6 +326,13 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
 
         dog_stats_api.increment("lms.courseware.question_answered", tags=tags)
 
+    def publish(block, event_type, event):
+        """A function that allows XModules to publish events."""
+        if event_type == 'grade':
+            handle_grade_event(block, event_type, event)
+        else:
+            track_function(event_type, event)
+
     # Build a list of wrapping functions that will be applied in order
     # to the Fragment content coming out of the xblocks that are about to be rendered.
     block_wrappers = []
diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt
index 9eda75f8a7f884259580e168ef7389ba77dde2ae..feca3fac5218223ee9cb0d32aa05e7984fee50ce 100644
--- a/requirements/edx/github.txt
+++ b/requirements/edx/github.txt
@@ -17,7 +17,7 @@
 -e git+https://github.com/appliedsec/pygeoip.git@95e69341cebf5a6a9fbf7c4f5439d458898bdc3b#egg=pygeoip
 
 # Our libraries:
--e git+https://github.com/edx/XBlock.git@6ec7edd6c44c7d2f1583df077cbf3e0f621ae984#egg=XBlock
+-e git+https://github.com/edx/XBlock.git@cfe5c37f98febd9a215d23cb206a25711056a142#egg=XBlock
 -e git+https://github.com/edx/codejail.git@e3d98f9455#egg=codejail
 -e git+https://github.com/edx/diff-cover.git@v0.2.9#egg=diff_cover
 -e git+https://github.com/edx/js-test-tool.git@v0.1.5#egg=js_test_tool