diff --git a/cbits/hs_zk.c b/cbits/hs_zk.c
--- a/cbits/hs_zk.c
+++ b/cbits/hs_zk.c
@@ -235,18 +235,7 @@
 }
 
 // ----------------------------------------------------------------------------
-
-zhandle_t* hs_zookeeper_init(HsStablePtr mvar, HsInt cap,
-                             hs_watcher_ctx_t* watcher_ctx, const char* host,
-                             int recv_timeout, const clientid_t* clientid,
-                             int flags) {
-  watcher_ctx->mvar = mvar;
-  watcher_ctx->cap = cap;
-  zhandle_t* zh = zookeeper_init(host, hs_zookeeper_watcher_fn, recv_timeout,
-                                 clientid, watcher_ctx, flags);
-  return zh;
-}
-
+// Operations
 // ----------------------------------------------------------------------------
 
 int hs_zoo_acreate(zhandle_t* zh, const char* path, const char* value,
diff --git a/src/ZooKeeper.hs b/src/ZooKeeper.hs
--- a/src/ZooKeeper.hs
+++ b/src/ZooKeeper.hs
@@ -3,7 +3,6 @@
   , I.zooSetDebugLevel
 
   , zookeeperResInit
-  , zookeeperResInitWithWatcher
   , Res.withResource
   , Res.Resource
 
@@ -34,26 +33,18 @@
 
     -- * Internal function (should NOT be used)
   , zookeeperInit
-  , zookeeperInitWithWatcher
   , zookeeperClose
   ) where
 
-import           Control.Concurrent       (forkIO, myThreadId, newEmptyMVar,
-                                           takeMVar, threadCapability)
-import           Control.Exception        (mask_, onException)
 import           Control.Monad            (void, when, zipWithM, (<=<))
 import           Data.Bifunctor           (first)
 import           Data.Maybe               (fromMaybe)
 import           Foreign.C                (CInt)
-import           Foreign.ForeignPtr       (mallocForeignPtrBytes,
-                                           touchForeignPtr, withForeignPtr)
 import           Foreign.Ptr              (FunPtr, Ptr, freeHaskellFunPtr,
-                                           nullPtr, plusPtr)
-import           GHC.Conc                 (newStablePtrPrimMVar)
-import           GHC.Stack                (HasCallStack, callStack)
+                                           nullFunPtr, nullPtr, plusPtr)
+import           GHC.Stack                (HasCallStack)
 import           Z.Data.CBytes            (CBytes)
 import qualified Z.Data.CBytes            as CBytes
-import qualified Z.Data.Text.Print        as Text
 import           Z.Data.Vector            (Bytes)
 import qualified Z.Foreign                as Z
 import qualified Z.IO.FileSystem.FilePath as ZF
@@ -66,37 +57,18 @@
 
 -------------------------------------------------------------------------------
 
+-- TODO: use ForeignPtr instead of resource style?
+
 -- | Create a resource of handle to used communicate with zookeeper.
 zookeeperResInit
-  :: HasCallStack
-  => CBytes
-  -- ^ host, comma separated host:port pairs, each corresponding to a zk
-  -- server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
-  -> CInt
-  -- ^ timeout
-  -> Maybe T.ClientID
-  -- ^ The id of a previously established session that this client will be
-  -- reconnecting to. Pass 'Nothing' if not reconnecting to a previous
-  -- session. Clients can access the session id of an established, valid,
-  -- connection by calling 'zooGetClientID'. If the session corresponding to
-  -- the specified clientid has expired, or if the clientid is invalid for
-  -- any reason, the returned 'T.ZHandle' will be invalid -- the 'T.ZHandle'
-  -- state will indicate the reason for failure (typically 'T.ZooExpiredSession').
-  -> CInt
-  -- ^ flags, reserved for future use. Should be set to zero.
-  -> Res.Resource T.ZHandle
-zookeeperResInit host timeout mclientid flags =
-  Res.initResource (zookeeperInit host timeout mclientid flags) zookeeperClose
-
-zookeeperResInitWithWatcher
   :: CBytes
   -- ^ host, comma separated host:port pairs, each corresponding to a zk
   -- server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
-  -> T.WatcherFn
+  -> Maybe T.WatcherFn
   -- ^ the global watcher callback function. When notifications are
   -- triggered this function will be invoked.
   -> CInt
-  -- ^ timeout
+  -- ^ session expiration time in milliseconds
   -> Maybe T.ClientID
   -- ^ The id of a previously established session that this client will be
   -- reconnecting to. Pass 'Nothing' if not reconnecting to a previous
@@ -108,9 +80,8 @@
   -> CInt
   -- ^ flags, reserved for future use. Should be set to zero.
   -> Res.Resource T.ZHandle
-zookeeperResInitWithWatcher host fn timeout mclientid flags = fst <$>
-  Res.initResource (zookeeperInitWithWatcher host fn timeout mclientid flags)
-                   (\(zh', fn')-> freeHaskellFunPtr fn' >> zookeeperClose zh')
+zookeeperResInit host fn timeout mclientid flags = fst <$>
+  Res.initResource (zookeeperInit host fn timeout mclientid flags) zookeeperClose
 
 -- | Create a node.
 --
@@ -678,49 +649,14 @@
 -- If it fails to create a new zhandle or not connected, an exception will be
 -- throwed.
 zookeeperInit
-  :: HasCallStack
-  => CBytes
-  -- ^ host, comma separated host:port pairs, each corresponding to a zk
-  -- server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
-  -> CInt
-  -- ^ timeout
-  -> Maybe T.ClientID
-  -- ^ The id of a previously established session that this client will be
-  -- reconnecting to. Pass 'Nothing' if not reconnecting to a previous
-  -- session. Clients can access the session id of an established, valid,
-  -- connection by calling 'zooGetClientID'. If the session corresponding to
-  -- the specified clientid has expired, or if the clientid is invalid for
-  -- any reason, the returned 'T.ZHandle' will be invalid -- the 'T.ZHandle'
-  -- state will indicate the reason for failure (typically 'T.ZooExpiredSession').
-  -> CInt
-  -- ^ flags, reserved for future use. Should be set to zero.
-  -> IO T.ZHandle
-zookeeperInit host timeout mclientid flags = do
-  let clientid = fromMaybe (I.ClientID nullPtr) mclientid
-  CBytes.withCBytesUnsafe host $ \host' -> mask_ $ do
-    mvar <- newEmptyMVar
-    sp <- newStablePtrPrimMVar mvar  -- freed by hs_try_takemvar()
-    ctx <- mallocForeignPtrBytes I.hsWatcherCtxSize
-    (ctxResult, zhResult) <- withForeignPtr ctx $ \ctx' -> do
-      (cap, _) <- threadCapability =<< myThreadId
-      zh <- I.c_hs_zookeeper_init sp cap ctx' host' timeout clientid flags
-      when (zh == I.ZHandle nullPtr) $ E.getCErrNum >>= flip E.throwZooError callStack
-      takeMVar mvar `onException` forkIO (do takeMVar mvar; touchForeignPtr ctx)
-      ctxData <- I.peekHsWatcherCtx ctx'
-      return (ctxData, zh)
-    case I.watcherCtxState ctxResult of
-      I.ZooConnectedState -> return zhResult
-      state -> E.throwIO $ E.ZINVALIDSTATE $ E.ZooExInfo (Text.toText state) callStack
-
-zookeeperInitWithWatcher
   :: CBytes
   -- ^ host, comma separated host:port pairs, each corresponding to a zk
   -- server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
-  -> T.WatcherFn
+  -> Maybe T.WatcherFn
   -- ^ the global watcher callback function. When notifications are
   -- triggered this function will be invoked.
   -> CInt
-  -- ^ timeout
+  -- ^ session expiration time in milliseconds
   -> Maybe T.ClientID
   -- ^ The id of a previously established session that this client will be
   -- reconnecting to. Pass 'Nothing' if not reconnecting to a previous
@@ -732,15 +668,17 @@
   -> CInt
   -- ^ flags, reserved for future use. Should be set to zero.
   -> IO (T.ZHandle, FunPtr I.CWatcherFn)
-zookeeperInitWithWatcher host fn timeout mclientid flags = do
+zookeeperInit host mfn timeout mclientid flags = do
   let clientid = fromMaybe (I.ClientID nullPtr) mclientid
-  fnPtr <- I.mkWatcherFnPtr fn
+  fnPtr <- maybe (pure nullFunPtr) I.mkWatcherFnPtr mfn
   zh <- CBytes.withCBytes host $ \host' -> do
     I.zookeeper_init host' fnPtr timeout clientid nullPtr flags
   return (zh, fnPtr)
 
-zookeeperClose :: T.ZHandle -> IO ()
-zookeeperClose = void . E.throwZooErrorIfNotOK <=< I.c_zookeeper_close_safe
+zookeeperClose :: (T.ZHandle, FunPtr I.CWatcherFn) -> IO ()
+zookeeperClose (zh, fnptr) = do
+  when (fnptr /= nullFunPtr) $ freeHaskellFunPtr fnptr
+  void $ E.throwZooErrorIfNotOK =<< I.c_zookeeper_close zh
 {-# INLINABLE zookeeperClose #-}
 
 -- | Return the client session id, only valid if the connections
diff --git a/src/ZooKeeper/Internal/FFI.hsc b/src/ZooKeeper/Internal/FFI.hsc
--- a/src/ZooKeeper/Internal/FFI.hsc
+++ b/src/ZooKeeper/Internal/FFI.hsc
@@ -53,15 +53,6 @@
   path <- CBytes.fromCString cpath
   fn zh (ZooEvent ev) (ZooState st) path
 
-foreign import ccall unsafe "hs_zk.h hs_zookeeper_init"
-  c_hs_zookeeper_init
-    :: StablePtr PrimMVar -> Int -> Ptr HsWatcherCtx
-    -> BA## Word8
-    -> CInt
-    -> ClientID
-    -> CInt
-    -> IO ZHandle
-
 foreign import ccall safe "zookeeper.h zookeeper_init"
   zookeeper_init
     :: Ptr Word8
@@ -73,7 +64,7 @@
     -> IO ZHandle
 
 foreign import ccall safe "hs_zk.h zookeeper_close"
-  c_zookeeper_close_safe :: ZHandle -> IO CInt
+  c_zookeeper_close :: ZHandle -> IO CInt
 
 foreign import ccall unsafe "hs_zk.h zoo_client_id"
   c_zoo_client_id :: ZHandle -> IO ClientID
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -21,7 +21,7 @@
 recvTimeout = 5000
 
 client :: Resource ZHandle
-client = zookeeperResInit "127.0.0.1:2182" recvTimeout Nothing 0
+client = zookeeperResInit "127.0.0.1:2182" Nothing recvTimeout Nothing 0
 
 main :: IO ()
 main = withResource client $ \zh -> do
diff --git a/zoovisitor.cabal b/zoovisitor.cabal
--- a/zoovisitor.cabal
+++ b/zoovisitor.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               zoovisitor
-version:            0.1.8.0
+version:            0.2.0.0
 synopsis:
   A haskell binding to Apache Zookeeper C library(mt) using Haskell Z project.
 
