diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for taskwarrior
 
+## 0.2.1.0
+
+* Add helpers for onAdd and onModify hooks.
+* Fix a bug where JSON without an `id` ar `urgency` key would be wrongly rejected.
+
 ## 0.2.0.0
 
 ### Breaking changes
diff --git a/src/Taskwarrior/IO.hs b/src/Taskwarrior/IO.hs
--- a/src/Taskwarrior/IO.hs
+++ b/src/Taskwarrior/IO.hs
@@ -6,6 +6,10 @@
   , saveTasks
   , createTask
   , getUUIDs
+  , onAdd
+  , onAddPure
+  , onModify
+  , onModifyPure
   )
 where
 
@@ -14,7 +18,9 @@
                                                 )
 import           Data.Text                      ( Text )
 import qualified Data.Text                     as Text
+import qualified Data.ByteString               as BS
 import qualified Data.ByteString.Lazy          as LBS
+                                         hiding ( putStrLn )
 import qualified Data.ByteString.Lazy.Char8    as LBS
 import qualified Data.Aeson                    as Aeson
 import           System.Process                 ( withCreateProcess
@@ -92,3 +98,31 @@
   uuid  <- getStdRandom random
   entry <- getCurrentTime
   pure $ makeTask uuid entry description
+
+-- | Takes a function @f originalTask modifiedTask = taskToSave@.
+-- The resulting IO action can be run as the `main :: IO ()` of a taskwarrior on-modify hook.
+onModifyPure :: (Task -> Task -> Task) -> IO ()
+onModifyPure f = onModify (\x y -> pure (f x y))
+
+onModifyError :: String
+onModifyError = "OnModify hook couldn‘t parse task."
+
+-- | Like onModifyPure but with side effects.
+onModify :: (Task -> Task -> IO Task) -> IO ()
+onModify f = do
+  original <- readTaskLine onModifyError
+  modified <- readTaskLine onModifyError
+  LBS.putStrLn . Aeson.encode =<< f original modified
+
+readTaskLine :: String -> IO Task
+readTaskLine errorMsg =
+  maybe (fail errorMsg) pure =<< Aeson.decode' . LBS.fromStrict <$> BS.getLine
+
+-- | Like onModifyPure but for the onAdd hook.
+onAddPure :: (Task -> Task) -> IO ()
+onAddPure f = onAdd (pure . f)
+
+-- | Like onAddPure with side effects.
+onAdd :: (Task -> IO Task) -> IO ()
+onAdd f = LBS.putStrLn . Aeson.encode =<< f =<< readTaskLine
+  "OnAdd hook couldn‘t parse task."
diff --git a/src/Taskwarrior/Task.hs b/src/Taskwarrior/Task.hs
--- a/src/Taskwarrior/Task.hs
+++ b/src/Taskwarrior/Task.hs
@@ -119,8 +119,8 @@
     status         <- Status.parseFromObject object
     recurringChild <- RecurringChild.parseFromObjectMay object
     uuid           <- object .: "uuid"
-    idRaw          <- object .: "id"
-    let id = if idRaw == 0 then Nothing else Just idRaw
+    idRaw          <- object .:? "id"
+    let id = if idRaw == Just 0 then Nothing else idRaw
     entry       <- object .: "entry" >>= Time.parse
     description <- object .: "description"
     start       <- parseTimeFromFieldMay "start"
@@ -134,7 +134,7 @@
       <$> parseFromFieldWithMay Priority.parseMay object "priority"
     depends <- maybe (pure []) parseUuidList (HashMap.lookup "depends" object)
     tags    <- Foldable.fold <$> object .:? "tags"
-    urgency <- object .: "urgency"
+    urgency <- fromMaybe 0 <$> object .:? "urgency"
     pure Task { until = until_, .. }
 
 parseFromFieldWithMay
diff --git a/taskwarrior.cabal b/taskwarrior.cabal
--- a/taskwarrior.cabal
+++ b/taskwarrior.cabal
@@ -4,7 +4,7 @@
 -- PVP summary:     +-+------- breaking API changes
 --                  | | +----- non-breaking API additions
 --                  | | | +--- code changes with no API change
-version: 0.2.0.0
+version: 0.2.1.0
 synopsis:           Types and aeson instances for taskwarrior tasks
 description:
   Types and aeson instances for the https://taskwarrior.org task import/export feature
