diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,9 @@
+## 1.2.2
+
+* We now use `stm-delay` to implement a timeout, if we don't *receive*
+  network traffic from the client. Under normal operation, the Socket.io
+  client should ping the server, so an idle session should remain alive.
+
 ## 1.2.1
 
 * Fixed a potential race condition in session allocation, where we could
diff --git a/engine-io.cabal b/engine-io.cabal
--- a/engine-io.cabal
+++ b/engine-io.cabal
@@ -1,5 +1,5 @@
 name: engine-io
-version: 1.2.1
+version: 1.2.2
 synopsis: A Haskell implementation of Engine.IO
 homepage: http://github.com/ocharles/engine.io
 license: BSD3
@@ -38,6 +38,7 @@
     monad-loops >=0.4 && <0.5,
     mwc-random >=0.13 && <0.14,
     stm >=2.4 && <2.5,
+    stm-delay >= 0.1.1 && <0.2,
     text >=0.11 && <1.3,
     transformers >=0.2 && <0.5,
     unordered-containers >=0.2 && <0.3,
diff --git a/src/Network/EngineIO.hs b/src/Network/EngineIO.hs
--- a/src/Network/EngineIO.hs
+++ b/src/Network/EngineIO.hs
@@ -71,13 +71,14 @@
 
 import qualified Control.Concurrent.Async as Async
 import qualified Control.Concurrent.STM as STM
+import qualified Control.Concurrent.STM.Delay as STMDelay
 import qualified Data.Aeson as Aeson
 import qualified Data.Attoparsec.ByteString as Attoparsec
 import qualified Data.Attoparsec.ByteString.Char8 as AttoparsecC8
 import qualified Data.ByteString as BS
-import qualified Data.ByteString.Char8 as BSChar8
 import qualified Data.ByteString.Base64 as Base64
 import qualified Data.ByteString.Builder as Builder
+import qualified Data.ByteString.Char8 as BSChar8
 import qualified Data.ByteString.Lazy as LBS
 import qualified Data.HashMap.Strict as HashMap
 import qualified Data.Text as Text
@@ -476,6 +477,10 @@
   app <- socketHandler socket
   userSpace <- liftIO $ Async.async (saApp app)
 
+  pingTimeoutDelay <- liftIO $ STMDelay.newDelay (pingTimeout * 1000000)
+  heartbeat <- liftIO $ Async.async $
+    STM.atomically (STMDelay.waitDelay pingTimeoutDelay)
+
   brain <- liftIO $ Async.async $ fix $ \loop -> do
     mMessage <- STM.atomically $ do
       transport <- STM.readTVar (socketTransport socket)
@@ -501,17 +506,21 @@
         ]
 
     case mMessage of
-      Just (Packet Close _) -> return ()
-      _ -> loop
+      Just (Packet Close _) ->
+        return ()
 
+      _ -> do
+        STMDelay.updateDelay pingTimeoutDelay (pingTimeout * 1000000)
+        loop
+
   _ <- liftIO $ Async.async $ do
-    _ <- Async.waitAnyCatchCancel [ userSpace, brain ]
+    _ <- Async.waitAnyCatchCancel [ userSpace, brain, heartbeat ]
     STM.atomically (STM.modifyTVar' (eioOpenSessions eio) (HashMap.delete (socketId socket)))
     saOnDisconnect app
 
   let openMessage = OpenMessage { omSocketId = socketId socket
                                 , omUpgrades = [ Websocket ]
-                                , omPingTimeout = 60000
+                                , omPingTimeout = pingTimeout * 1000
                                 , omPingInterval = 25000
                                 }
 
@@ -520,6 +529,9 @@
 
   writeBytes api (encodePayload supportsBinary payload)
 
+  where
+
+  pingTimeout = 60
 
 --------------------------------------------------------------------------------
 upgrade :: MonadIO m => ServerAPI m -> Socket -> m ()
