     (gevents/listen w goog.net.WebSocket.EventType.OPENED
                     #(do
                        (utils/mlog "opened" %)
-                       (swap! tal-state (fn [s]
-                                          (-> s
-                                            (assoc :open? true)
-                                            (dissoc :closed? :close-code :close-reason))))
+                       (let [timer-id (start-ping tal-state)]
+                         (swap! tal-state (fn [s]
+                                            (-> s
+                                              (assoc :open? true
+                                                     :keep-alive-timer timer-id)
+                                              (dissoc :closed? :close-code :close-reason)))))
                        (start-send-queue tal-state)
+
                        (when (fn? on-open)
                          (on-open tal-state))))
     (gevents/listen w goog.net.WebSocket.EventType.CLOSED

    (gevents/listen w goog.net.WebSocket.EventType.MESSAGE
                    #(do
                       ;;(utils/mlog "message" %)
                       (swap! tal-state assoc :last-message-time (js/Date.))
                       (swap! recv-queue (fn [q] (apply conj q (decode-msg (.-message %)))))))
    (.open w url)
    tal-state))

    (loop [msg (pop-atom send-queue)]
      (when msg
        (send-msg ws msg)
        (swap! tal-state assoc :last-send-time (js/Date.))
        (recur (pop-atom send-queue))))))

(defn start-send-queue [tal-state]
