diff --git a/ipython-kernel.cabal b/ipython-kernel.cabal
--- a/ipython-kernel.cabal
+++ b/ipython-kernel.cabal
@@ -1,5 +1,5 @@
 name:                ipython-kernel
-version:             0.10.1.0
+version:             0.10.2.0
 synopsis:            A library for creating kernels for IPython frontends
 
 description:         ipython-kernel is a library for communicating with frontends for the interactive IPython framework. It is used extensively in IHaskell, the interactive Haskell environment.
@@ -52,7 +52,8 @@
                        transformers    ,
                        unordered-containers,
                        uuid            ,
-                       zeromq4-haskell
+                       zeromq4-haskell ,
+                       parsec
 
 -- Example program
 executable simple-calc-example
diff --git a/src/IHaskell/IPython/EasyKernel.hs b/src/IHaskell/IPython/EasyKernel.hs
--- a/src/IHaskell/IPython/EasyKernel.hs
+++ b/src/IHaskell/IPython/EasyKernel.hs
@@ -125,7 +125,7 @@
       err = error $ "No reply for message " ++ show (mhMsgType parent)
 
   return $ MessageHeader (mhIdentifiers parent) (Just parent) (Metadata (HashMap.fromList []))
-            newMessageId (mhSessionId parent) (mhUsername parent) repType
+            newMessageId (mhSessionId parent) (mhUsername parent) repType []
 
 
 -- | Execute an IPython kernel for a config. Your 'main' action should call this as the last thing
@@ -144,7 +144,7 @@
     repHeader <- createReplyHeader (header req)
     when (debug config) . liftIO $ print req
     reply <- replyTo config execCount zmq req repHeader
-    liftIO $ writeChan (shellRequestChannel zmq) reply
+    liftIO $ writeChan (shellReplyChannel zmq) reply
 
 replyTo :: MonadIO m
         => KernelConfig m output result
diff --git a/src/IHaskell/IPython/Message/Parser.hs b/src/IHaskell/IPython/Message/Parser.hs
--- a/src/IHaskell/IPython/Message/Parser.hs
+++ b/src/IHaskell/IPython/Message/Parser.hs
@@ -22,14 +22,16 @@
 type LByteString = Lazy.ByteString
 
 -- --- External interface ----- | Parse a message from its ByteString components into a Message.
+--   See https://jupyter-client.readthedocs.io/en/stable/messaging.html#the-wire-protocol
 parseMessage :: [ByteString] -- ^ The list of identifiers sent with the message.
              -> ByteString   -- ^ The header data.
              -> ByteString   -- ^ The parent header, which is just "{}" if there is no header.
              -> ByteString   -- ^ The metadata map, also "{}" for an empty map.
              -> ByteString   -- ^ The message content.
+             -> [ByteString] -- ^ Extra raw data buffer(s)
              -> Message      -- ^ A parsed message.
-parseMessage idents headerData parentHeader metadata content =
-  let header = parseHeader idents headerData parentHeader metadata
+parseMessage idents headerData parentHeader metadata content buffers =
+  let header = parseHeader idents headerData parentHeader metadata buffers
       messageType = mhMsgType header
       messageWithoutHeader = parser messageType $ Lazy.fromStrict content
   in messageWithoutHeader { header = header }
@@ -39,16 +41,17 @@
             -> ByteString    -- ^ The header data.
             -> ByteString    -- ^ The parent header, or "{}" for Nothing.
             -> ByteString    -- ^ The metadata, or "{}" for an empty map.
+            -> [ByteString]  -- ^ Extra raw data buffer(s)
             -> MessageHeader -- The resulting message header.
-parseHeader idents headerData parentHeader metadata =
-  MessageHeader idents parentResult metadataMap messageUUID sessionUUID username messageType
+parseHeader idents headerData parentHeader metadata buffers =
+  MessageHeader idents parentResult metadataMap messageUUID sessionUUID username messageType buffers
   where
     -- Decode the header data and the parent header data into JSON objects. If the parent header data is
     -- absent, just have Nothing instead.
     Just result = decode $ Lazy.fromStrict headerData :: Maybe Object
     parentResult = if parentHeader == "{}"
                      then Nothing
-                     else Just $ parseHeader idents parentHeader "{}" metadata
+                     else Just $ parseHeader idents parentHeader "{}" metadata []
 
     Success (messageType, username, messageUUID, sessionUUID) = flip parse result $ \obj -> do
       messType <- obj .: "msg_type"
diff --git a/src/IHaskell/IPython/Types.hs b/src/IHaskell/IPython/Types.hs
--- a/src/IHaskell/IPython/Types.hs
+++ b/src/IHaskell/IPython/Types.hs
@@ -153,6 +153,7 @@
          , mhSessionId :: UUID                    -- ^ A unique session UUID.
          , mhUsername :: Username                 -- ^ The user who sent this message.
          , mhMsgType :: MessageType               -- ^ The message type.
+         , mhBuffers :: [ByteString]              -- ^ Extra raw data buffer(s)
          }
   deriving (Show, Read)
 
@@ -779,6 +780,7 @@
               | MimeVega
               | MimeVegalite
               | MimeVdom
+              | MimeCustom Text
   deriving (Eq, Typeable, Generic)
 
 -- Extract the plain text from a list of displays.
@@ -806,6 +808,7 @@
   show MimeVega = "application/vnd.vega.v2+json"
   show MimeVegalite = "application/vnd.vegalite.v2+json"
   show MimeVdom = "application/vdom.v1+json"
+  show (MimeCustom custom) = Text.unpack custom
 
 instance Read MimeType where
   readsPrec _ "text/plain" = [(PlainText, "")]
@@ -822,7 +825,7 @@
   readsPrec _ "application/vnd.vega.v2+json" = [(MimeVega, "")]
   readsPrec _ "application/vnd.vegalite.v1+json" = [(MimeVegalite, "")]
   readsPrec _ "application/vdom.v1+json" = [(MimeVdom, "")]
-  readsPrec _ _ = []
+  readsPrec _ t = [(MimeCustom (Text.pack t), "")]
 
 -- | Convert a MIME type and value into a JSON dictionary pair.
 displayDataToJson :: DisplayData -> (Text, Value)
diff --git a/src/IHaskell/IPython/ZeroMQ.hs b/src/IHaskell/IPython/ZeroMQ.hs
--- a/src/IHaskell/IPython/ZeroMQ.hs
+++ b/src/IHaskell/IPython/ZeroMQ.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE OverloadedStrings, DoAndIfThenElse #-}
+{-# LANGUAGE OverloadedStrings, DoAndIfThenElse, FlexibleContexts #-}
 
 -- | Description : Low-level ZeroMQ communication wrapper.
 --
@@ -30,6 +30,7 @@
 import qualified Data.Text.Encoding as Text
 import           System.ZMQ4 as ZMQ4
 import           Text.Read (readMaybe)
+import           Text.Parsec (runParserT, manyTill, anyToken, (<|>), eof, tokenPrim, incSourceColumn)
 
 import           IHaskell.IPython.Message.Parser
 import           IHaskell.IPython.Types
@@ -268,38 +269,28 @@
 -- | Receive and parse a message from a socket.
 receiveMessage :: Receiver a => Bool -> Socket a -> IO Message
 receiveMessage debug sock = do
-  -- Read all identifiers until the identifier/message delimiter.
-  idents <- readUntil "<IDS|MSG>"
-
-  -- Ignore the signature for now.
-  void next
-
-  headerData <- next
-  parentHeader <- next
-  metadata <- next
-  content <- next
-
-  when debug $ do
-    putStr "Header: "
-    Char.putStrLn headerData
-    putStr "Content: "
-    Char.putStrLn content
-
-  return $ parseMessage idents headerData parentHeader metadata content
-
+  blobs <- receiveMulti sock
+  runParserT parseBlobs () "" blobs >>= \r -> case r of
+    Left parseerr -> fail $ "Malformed Wire Protocol message: " <> show parseerr
+    Right (idents, headerData, parentHeader, metaData, content, buffers) -> do
+      when debug $ do
+        putStr "Header: "
+        Char.putStrLn headerData
+        putStr "Content: "
+        Char.putStrLn content
+      return $ parseMessage idents headerData parentHeader metaData content buffers
   where
-    -- Receive the next piece of data from the socket.
-    next = receive sock
-
-    -- Read data from the socket until we hit an ending string. Return all data as a list, which does
-    -- not include the ending string.
-    readUntil str = do
-      line <- next
-      if line /= str
-        then do
-          remaining <- readUntil str
-          return $ line : remaining
-        else return []
+    parseBlobs = do
+      idents       <- manyTill anyToken (satisfy (=="<IDS|MSG>"))
+      _            <- anyToken <|> fail "No signature"
+      headerData   <- anyToken <|> fail "No headerData"
+      parentHeader <- anyToken <|> fail "No parentHeader"
+      metaData     <- anyToken <|> fail "No metaData"
+      content      <- anyToken <|> fail "No contents"
+      buffers      <- manyTill anyToken eof
+      pure (idents, headerData, parentHeader, metaData, content, buffers)
+    satisfy f = tokenPrim Char.unpack (\pos _ _ -> incSourceColumn pos 1)
+                                      (\c -> if f c then Just c else Nothing)
 
 -- | Encode a message in the IPython ZeroMQ communication protocol and send it through the provided
 -- socket. Sign it using HMAC with SHA-256 using the provided key.
@@ -320,10 +311,18 @@
   sendPiece parentHeaderStr
   sendPiece metadata
 
-  -- Conclude transmission with content.
-  sendLast content
+  -- If there are no mhBuffers, then conclude transmission with content.
+  case mhBuffers hdr of
+    [] -> sendLast content
+    _  -> sendPiece content
 
+  sendBuffers $ mhBuffers hdr
+
   where
+    sendBuffers [] = pure ()
+    sendBuffers [b] = sendLast b
+    sendBuffers (b:bs) = sendPiece b >> sendBuffers bs
+
     sendPiece = send sock [SendMore]
     sendLast = send sock []
 
